dpi定义为以s1[i-1]和s2[j-1]结尾的公共子串长度,因最长公共子串要求连续,故仅当字符相等时继承左上值加1,否则置0;回溯时子串起始下标为end_i - max_len,需确保不越界。

为什么 dp[i][j] 要定义为「以 s1[i-1] 和 s2[j-1] 结尾的公共子串长度」
因为最长公共子串必须连续,所以状态只能依赖「上一个字符恰好匹配」的情形。若定义成「前 i 个和前 j 个字符的最长公共子串长度」,就无法区分连续与非连续(那其实是 LCS 的定义)。这个细节错一点,整个表就回溯不出正确子串。
实操建议:
-
dp数组维度为(m+1) x (n+1),首行首列全 0,避免边界判断 - 只在
s1[i-1] == s2[j-1]时更新:dp[i][j] = dp[i-1][j-1] + 1;否则置 0 - 边填表边记录最大值
max_len和对应结尾位置end_i(即i),方便后续截取
如何从 dp 表安全回溯出子串,而不是越界或取错范围
不能直接用 max_len 往前数 —— 因为 dp 表里存的是「结尾位置」的长度,真正子串起始索引是 end_i - max_len,但这个 end_i 是 dp 行索引,对应原字符串下标是 end_i - 1。所以起始下标实际是 (end_i - 1) - max_len + 1 == end_i - max_len。
常见错误现象:
立即学习“C++免费学习笔记(深入)”;
- 取
s1.substr(end_i - max_len, max_len)却没检查end_i - max_len (当 <code>max_len == 0时发生) - 误用
dp中的j坐标去截s1,导致字符串错位 - 忘记
max_len == 0时应返回空串,而非崩溃或乱码
C++ 实现中 vector<vector>></vector> 的性能与初始化陷阱
二维 vector 动态分配在多次调用时开销明显,尤其字符串很长时。更关键的是:若用 vector<vector>>(m+1, vector<int>(n+1, 0))</int></vector>,内部会先构造一个 vector<int>(n+1, 0)</int>,再复制 m+1 次 —— 复制的是对象,不是引用,效率低且易被误认为“已优化”。
实操建议:
- 优先用一维数组模拟二维:索引
i * (n+1) + j,内存连续,缓存友好 - 若坚持二维
vector,改用resize分步构造:dp.resize(m+1); for (int i = 0; i - 对空输入或极短输入(如一方为空),提前返回,避免建表
完整可运行片段:含边界处理与 const 正确性
#include <string>
#include <vector>
#include <algorithm>
<p>std::string longestCommonSubstring(const std::string& s1, const std::string& s2) {
if (s1.empty() || s2.empty()) return "";
int m = s1.size(), n = s2.size();
std::vector<std::vector<int>> dp(m + 1, std::vector<int>(n + 1, 0));
int max_len = 0, end_i = 0;</p><pre class='brush:php;toolbar:false;'>for (int i = 1; i <= m; ++i) {
for (int j = 1; j <= n; ++j) {
if (s1[i-1] == s2[j-1]) {
dp[i][j] = dp[i-1][j-1] + 1;
if (dp[i][j] > max_len) {
max_len = dp[i][j];
end_i = i;
}
} else {
dp[i][j] = 0;
}
}
}
if (max_len == 0) return "";
return s1.substr(end_i - max_len, max_len);}
注意 s1 和 s2 用 const std::string& 传入,避免拷贝;substr 第二参数是长度,不是结束下标 —— 这个参数意义容易和 Python 的切片混淆。
真正难的不是填表,而是确认「哪个字符串负责提供子串结果」以及「下标偏移是否统一按 0 起始推导」。这两个点错一个,输出就完全不对,而且调试时很难一眼看出是逻辑错还是索引错。


















