最长公共子序列
问题描述
子序列:将当前序列去掉零个或多个元素所得序列。
公共子序列:同时满足是两个序列的子序列的序列。
最长公共子序列问题:求解两个序列的公共子序列,使得它尽可能长。
形式化定义
输入:
- 序列
- 序列
输出:
- 公共子序列
优化目标:
约束条件:
问题分析
令 表示 和 的最长公共子序列长度,那么原始问题就是求解 。
下面我们来考察末尾字符,分为如下两大类情况:
情况一
如果 ,那么需要考察 和 两个子问题。并且当前问题的解是这两个子问题解的最大值,即如下的状态转移方程:
情况二
如果 ,那么既可以匹配这最后一对字符,也可以不匹配。这样一来,需要考察 、 和 三个子问题。
问题:三个子问题是否都需要求解?
分析:
- 比 至多大 ,即 。
- 比 至多大 ,即 。
因此
故只需要考虑子问题 即可。于是状态转移方程为
小结
综上所述,状态转移方程为
算法代码
string longestCommonSubsequence(const string &x, const string &y) {
enum Drct {
LU, // 向左上方回溯
U, // 向上方回溯
L // 向左边回溯
};
auto lengthX = x.size(), lengthY = y.size();
// C[i,j] 表示 x[1..i] 和 y[1..j] 的最长公共子序列长度
vector<vector<int>> C(lengthX + 1, vector<int>(lengthY + 1, 0));
// rec[i,j] 是追踪数组,记录子问题来源
vector<vector<Drct>> rec(lengthX + 1, vector<Drct>(lengthY + 1));
for (int i = 1; i <= lengthX; ++i) {
for (int j = 1; j <= lengthY; ++j) {
if (x[i - 1] == y[j - 1]) {
C[i][j] = C[i - 1][j - 1] + 1;
rec[i][j] = LU;
} else {
C[i][j] = C[i - 1][j] > C[i][j - 1] ? C[i - 1][j] : C[i][j - 1];
rec[i][j] = C[i - 1][j] > C[i][j - 1] ? U : L;
}
}
}
int maxLength = (lengthX < lengthY ? lengthX : lengthY) + 1;
char *pStr = new char[maxLength];
char *p = pStr + maxLength - 1;
*p = '\0'; // 字符串末尾结束符
// 根据追踪数组回溯最长公共子序列
for (int i = lengthX, j = lengthY; i > 0 && j > 0;) {
if (rec[i][j] == LU) {
*(--p) = x[i - 1]; // LU 才说明 x[i]==y[j],该字符是最长公共子序列的一部分
--i;
--j;
} else if (rec[i][j] == L) {
--j;
} else if (rec[i][j] == U) {
--i;
}
}
return string(p);
}
算法分析
- 时间复杂度:
- 空间复杂度: