dpi定义为text1[0..i-1]和text2[0..j-1]的LCS长度,因1-based语义避免越界与特判,初始化dp0和dp*为0,转移时用text1[i-1]和text2[j-1]对齐下标。

为什么 dp[i][j] 要定义成 “text1[0..i-1] 和 text2[0..j-1] 的 LCS 长度”
因为下标对齐更安全,避免边界越界和逻辑错位。如果定义成 dp[i][j] 表示包含第 i、j 个字符的子问题,那 i=0 或 j=0 时就得反复特判空串——实际写起来容易漏掉 dp[0][j] 或 dp[i][0] 的初始化,导致结果偏小或运行时越界。
实操建议:
- 统一用 1-based 索引语义:申请
dp数组大小为(m+1) x (n+1),其中m = text1.length(),n = text2.length() - 初始化整行
dp[0][j] = 0、整列dp[i][0] = 0,不用循环里嵌条件判断 - 状态转移只在
i >= 1 && j >= 1时进行,干净利落
状态转移写成 text1[i-1] == text2[j-1] 还是 text1[i] == text2[j]
取决于你 dp 的定义方式。一旦选了“前 i 个字符”,就必须用 i-1 去访问 text1,否则下标直接越界——这是 C++ 里最常触发 std::out_of_range 或静默读脏内存的地方。
常见错误现象:
立即学习“C++免费学习笔记(深入)”;
- 把
dp[i][j] = dp[i-1][j-1] + 1写成dp[i][j] = dp[i][j] + 1(忘了来源) - 比较时写
text1[i] == text2[j],但i最大到m,而text1[m]是 '\0',比较永远不成立 - 漏写 else 分支,导致未赋值的
dp[i][j]保持随机初值(尤其用vector<vector>>(m+1, vector<int>(n+1))</int></vector>时不会自动填 0?错!它会零初始化,但手写数组如int dp[1001][1001]就不会)
空间优化:从二维 dp 到一维 dp 怎么改才不出错
能省空间,但不能省思考。二维转一维的核心是:每次只依赖上一行和左边一个值,所以用滚动数组,但必须从右往左更新,否则左边新值会覆盖掉还没用上的旧值。
实操建议:
- 声明
vector<int> dp(n+1, 0)</int>,外层遍历i(text1 索引),内层倒序遍历j从n到1 - 需要临时存上一轮的左上角值:
int prev = dp[j-1],因为在修改dp[j-1]前先记下它 - 别试图用两个变量替代整个一维数组——当
text2很长时,你需要全部j位置的历史值,不是只记一个 - 调试时可加一句
if (i == 1 && j == 1) cout 快速验证初始状态是否符合预期
返回值到底是 dp[m][n] 还是 dp[m-1][n-1]
是 dp[m][n]。因为你的 dp 定义覆盖了完整输入范围:text1[0..m-1] 共 m 个字符,对应 dp[m][*];同理 text2[0..n-1] 对应 dp[*][n]。所以最终答案落在右下角 dp[m][n],不是 dp[m-1][n-1]。
这个点容易被类比其他 DP(比如最长上升子序列)带偏——LCS 是双字符串匹配,维度天然多一层,边界多留一行一列不是冗余,是必要缓冲。
如果发现输出总是比预期小 1,八成是这里取错了下标,或者初始化时没把 dp[0][j] 全设为 0。










