二维DP数组开dpm+1是为了自然容纳空字符串边界,使dp0和dp*初始化为0,避免下标越界与逻辑错误,实际匹配从i=1,j=1开始,答案在dpm。
dp[m+1][n+1] 而不是 dp[m][n]
因为状态转移依赖「空字符串」作为边界:当 i=0(s1 为空)或 j=0(s2 为空)时,LCS 长度必为 0。开 m+1 行 n+1 列可自然容纳这些边界,避免每次判断 i==0 || j==0。否则容易在循环里写错下标,比如把 dp[i-1][j-1] 写成 dp[i][j] 导致越界或逻辑错误。
dp[0][*] 和 dp[*][0] 自动满足边界条件i=1, j=1 开始,对应 s1[0] 和 s2[0]
dp[m][n],不是 dp[m-1][n-1]
dp[i][j] 的状态转移怎么写才不漏情况核心就两种情况,必须覆盖所有分支:
if (s1[i-1] == s2[j-1]) {
dp[i][j] = dp[i-1][j-1] + 1;
} else {
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
max(...),否则可能丢掉这个唯一最优路径i-1 和 j-1,因为 dp 下标比字符串下标大 1dp 数组还原出具体的 LCS 字符串反向遍历 dp 数组,从 dp[m][n] 往回走,靠比较值的变化来决定路径:
string lcs = ""; int i = m, j = n; while (i > 0 && j > 0) { if (s1[i-1] == s2[j-1]) { lcs += s1[i-1]; i--; j--; } else if (dp[i-1][j] > dp[i][j-1]) { i--; } else { j--; } } reverse(lcs.begin(), lcs.end());
dp 值确实来自左上角(即 dp[i][j] == dp[i-1][j-1] + 1)才加入结果dp[i-1][j] > dp[i][j-1] 表示当前值继承自上方,说明 s1[i-1] 没被选中==),任选其一即可,但代码里用了 >= 的等效写法(else 分支兜底),这是常见且安全的写法可以滚动数组优化,但仅适用于求长度,不适用于还原字符串。因为还原需要整张表的路径信息。
prev[j] 和 curr[j],每次迭代后交换curr[j] 依赖 curr[j-1](左)和 prev[j-1](左上)dp[j]),需临时存 dp[j-1] 前的值,否则被覆盖;常见错误是没保存 prev[j-1] 就覆盖了