最长公共子序列(LCS)
💡 核心思想
最长公共子序列(LCS)是找出两个序列的最长公共子序列(不要求连续)。经典解法是二维 DP:dp[i][j] 表示 a[1..i] 和 b[1..j] 的 LCS 长度。当 a[i] == b[j] 时,dp[i][j] = dp[i-1][j-1] + 1;否则 dp[i][j] = max(dp[i-1][j], dp[i][j-1])。
LCS 是二维 DP 的入门模板,理解了 LCS 才能理解编辑距离、最长公共上升子序列(LCIS)等进阶问题。很多选手看到两个序列就慌,其实画个表格就清楚了:行是一个序列,列是另一个序列,相等就往对角线加 1,不相等就取左边或上边的最大值。
🎯 直觉理解
想象你在比较两串DNA,找它们共同的基因片段。LCS 就是"不需要连续"的共同片段。比如 "ABCBDAB" 和 "BDCABA" 的 LCS 是 "BCBA"(长度为 4)。你可以画一个表格,一行一列地填,就像玩数独。
📝 算法流程
- 初始化 dp[n+1][m+1] 全为 0
- 遍历 i 从 1 到 n,j 从 1 到 m:
- 如果 a[i] == b[j],则 dp[i][j] = dp[i-1][j-1] + 1
- 否则 dp[i][j] = max(dp[i-1][j], dp[i][j-1])
- 答案为 dp[n][m]
- 如需输出方案,从 dp[n][m] 倒推
$$dp[i][j] = \begin{cases} dp[i-1][j-1] + 1 & a_i = b_j \\ \max(dp[i-1][j], dp[i][j-1]) & a_i \neq b_j \end{cases}$$
📊 复杂度分析
| 指标 | 复杂度 |
|---|---|
| 时间 | $O(n \cdot m)$ |
| 空间 | $O(n \cdot m)$(可优化至 $O(m)$) |
💻 参考实现(C++)
C++ (C++17)
#include
using namespace std;
// LCS 长度
int lcs(string a, string b) {
int n = a.size(), m = b.size();
vector> dp(n + 1, vector(m + 1, 0));
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
if (a[i-1] == b[j-1]) dp[i][j] = dp[i-1][j-1] + 1;
else dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
return dp[n][m];
}
// LCS 具体方案(倒推)
string lcsPath(string a, string b) {
int n = a.size(), m = b.size();
vector> dp(n + 1, vector(m + 1, 0));
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
if (a[i-1] == b[j-1]) dp[i][j] = dp[i-1][j-1] + 1;
else dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
string res;
int i = n, j = m;
while (i > 0 && j > 0) {
if (a[i-1] == b[j-1]) { res += a[i-1]; i--; j--; }
else if (dp[i-1][j] > dp[i][j-1]) i--;
else j--;
}
reverse(res.begin(), res.end());
return res;
}
int main() {
string a = "ABCBDAB", b = "BDCABA";
cout << lcs(a, b) << endl; // 4
cout << lcsPath(a, b) << endl; // BCBA
return 0;
} ⚠️ 常见坑点
下标从 0 还是从 1 开始——dp 数组建议从 1 开始,避免处理边界
只输出长度忘记输出方案——倒推时要注意三个分支的判断
空间优化时覆盖问题——滚动数组要从左到右还是右到左
LCS 与最长公共子串混淆——子串要求连续,子序列不要求
📚 相关题目
| 题目 | 来源 | 难度 | 备注 |
|---|---|---|---|
| P1439 最长公共子序列 | 洛谷 | CSP-S | LCS 模板题 |
| P1541 乌龟棋 | 洛谷 | CSP-S | 多维 DP 变体 |
| P2758 编辑距离 | 洛谷 | CSP-S | LCS 的扩展 |