dpi表示s1前i个字符与s2前j个字符的最小编辑距离,故维度为(m+1)×(n+1),初始化dp0=j、dpi=i;状态转移需比较s1[i-1]与s2[j-1],相同时取dpi-1,不同时取min(dpi-1+1, dpi-1+1, dpi+1);填表须按从左到右、从上到下顺序以保证依赖值已计算。

dp[i][j] 的定义和初始化为什么必须是「前 i 个字符」而非「第 i 个字符」
填表前最容易卡住的地方,是误把 dp[i][j] 理解成「s1 的第 i 个字符到 s2 的第 j 个字符的编辑距离」。实际它表示的是 s1.substr(0, i)(即前 i 个字符)与 s2.substr(0, j) 的最小编辑距离。因此维度是 (m+1) × (n+1),首行首列对应空字符串参与比较。
初始化逻辑很直接:
-
dp[0][j] = j:空串变s2前 j 个字符,只能插入 j 次 -
dp[i][0] = i:s1前 i 个字符变空串,只能删除 i 次
不按这个定义初始化,后续状态转移会全错——比如 dp[1][1] 就无法正确反映两个单字符的比较结果。
状态转移方程里 s1[i-1] == s2[j-1] 时为什么直接取 dp[i-1][j-1]
因为当前字符相同,不需要任何操作,问题自然缩小为子问题:前 i−1 和前 j−1 个字符的距离。这里下标偏移是关键——i 和 j 是长度,而字符串索引从 0 开始,所以比较的是 s1[i-1] 和 s2[j-1]。
当字符不同时,三种操作对应三个来源:
- 替换:
dp[i-1][j-1] + 1(改掉s1[i-1]使其等于s2[j-1]) - 删除:
dp[i-1][j] + 1(删掉s1[i-1],再处理剩下 i−1 个对 j 个) - 插入:
dp[i][j-1] + 1(在s1末尾插入s2[j-1],相当于用 i 个字符去匹配 j−1 个)
三者取最小值即可。漏掉任意一种,表格就可能填错——尤其容易忽略「插入」对应的是 dp[i][j-1] 而不是 dp[i-1][j-1]。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
填表顺序为什么必须是「从左到右、从上到下」
因为每个 dp[i][j] 都依赖 dp[i-1][j]、dp[i][j-1] 和 dp[i-1][j-1],这三个位置都在当前格子的上方、左方或左上方。如果乱序填(比如先填右下角),所需前置值还没算出来,结果必然是错的。
实操建议:
- 用二维 vector 初始化为 0,先填第 0 行第 0 列
- 外层循环
i从 1 到 m,内层j从 1 到 n - 每填一个格子,立刻验证它是否符合上述三个依赖关系
常见错误是循环变量写反(比如 j 在外层),导致依赖未就绪;或者边界条件没处理好,让 i-1 或 j-1 下标越界。
调试时怎么快速定位填错的格子
最有效的方法是手写小样例(如 s1 = "ab", s2 = "a"),画出 3×2 表格,逐格手动推导并和代码输出比对。重点关注:
-
dp[1][1]:应为 0("a" → "a") -
dp[2][1]:应为 1("ab" → "a",删 'b') -
dp[1][2]:不可能出现(s2 长度为 1,j 最大为 1),说明循环上限写错了
如果某格数值异常,立刻检查该格的三个上游值是否已计算、字符比较是否用了 i-1/j-1、min 函数有没有括号包全三个表达式。动态规划填表本身不难,但差一个下标或一个加 1,整张表就崩了。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










