最长公共子序列 最长公共子串
字符串中常见的用到动态规划的题目,求最长公共子序列和最长公共子串。
1. 最长公共子序列
1.1 求最长公共子序列的值
子串和子序列是不同的,子串要求连续。
《算法导论》第15章动态规划就是讲最长公共子序列。参考七月算法官网的LCS的视频,讲得很清晰。
得出下面的递推公式:
显然属于动态规划的题目。
int lcs(string str1, string str2) {
int len1 = str1.length();
int len2 = str2.length();
int n = len1 + 1;
int m = len2 + 1;
int i, j;
vector<vector<int> > res(n, vector<int>(m));
for (j = 0; j < m; j ++)
res[0][j] = 0;
for (i = 0; i < n; i ++)
res[i][0] = 0;
for (i = 1; i < n; i ++)
for (j = 1; j < m; j ++) {
if (str1[i - 1] == str2[j - 1]) {
res[i][j] = res[i - 1][j - 1] + 1;
} else {
res[i][j] = max(res[i - 1][j], res[i][j - 1]);
}
}
return res[n - 1][m - 1];
}
自底向上的动态规划,时间复杂度是O(N*M),空间复杂度分析O(N*M),因为求res二维数组中的每一个值。
如果只是求最长公共子序列的长度,空间复杂度是可以优化的。
参考:
[1] http://blog.csdn.net/crayondeng/article/details/14442645
1.2 返回最长公共子序列
如果仅仅是求长公共子序列的值,上述解法可以。如果要求返回的是最长公共子序列,需要维护一个表direc来构造二维数组。
代码如下:
int lcs(string str1, string str2, vector<vector<int> > &direc) {
int len1 = str1.length();
int len2 = str2.length();
int n = len1 + 1;
int m = len2 + 1;
int i, j;
vector<vector<int> > res(n, vector<int>(m));
for (j = 0; j < m; j ++)
res[0][j] = 0;
for (i = 0; i < n; i ++)
res[i][0] = 0;
for (i = 1; i < n; i ++)
for (j = 1; j < m; j ++) {
if (str1[i - 1] == str2[j - 1]) {
res[i][j] = res[i - 1][j - 1] + 1;
direc[i][j] = 0;
} else if (res[i - 1][j] > res[i][j - 1]){
res[i][j] = res[i - 1][j];
direc[i][j] = 1;
} else {
res[i][j] = res[i][j - 1];
direc[i][j] = -1;
}
}
return res[n - 1][m - 1];
}
void PrintLCS(string str1, vector<vector<int> > direc, int i, int j) {
if (i == 0 || j == 0)
return;
if (direc[i][j] == 0) {
PrintLCS(str1, direc, i - 1, j - 1);
/* 第i行元素对应的str1中是i-1的下标 */
cout << str1[i - 1];
} else if (direc[i][j] == 1) {
PrintLCS(str1, direc, i - 1, j);
} else {
PrintLCS(str1, direc, i, j - 1);
}
}
按照表direc构造最长公共子序列,是逆序的,所以我们可以用递归过程按正确的顺序打印出X和Y的一个LCS。
参考:
[1] 七月算法的PPT
[2]《算法导论》第15章动态规划
[3] http://blog.csdn.net/hackbuteer1/article/details/6686925