最长公共子序列(LCS)
今天腾讯实习二面被问到这题,没有想出来,后来问同学才知道这是个动态规划的经典问题,所以写篇随笔记录一下:
字符串的子序列:
一个字符串的子序列,是指从该字符串中去掉任意多个字符后剩下的字符在不改变顺序的情况下组成的新字符串。
最长公共子序列,是指多个字符串可具有的长度最大的公共的子序列。
例如
字符串S1 = "ABCDAB";
字符串S2 = "ADCDECB";
最长公共子序列为:ACDB(红色部分)
现用动态规划方法计算LCS:
假设有字符串a[1~m] b[1~n] ,设置辅助数组c[i][j]表示:字符串a[1~i],b[1~j]中的最长公共子序列的长度则有如下动态规划表达式:
程序如下:
1 #include<iostream> 2 #include<string> 3 using namespace std; 4 5 #define MAX_LENGTH 100 6 7 //c[i,j]表示字符串a[0~i] b[0~j]中最大子序列的长度 8 int c[MAX_LENGTH+1][MAX_LENGTH+1]; 9 10 #define MAX_VALUE(x,y) x>y?x:y 11 12 //返回两个字符串的最大子序列长度, 13 int LCS(string a, string b) 14 { 15 //让字符串的下标从1开始 16 a.insert(a.begin(), 0); 17 b.insert(b.begin(), 0); 18 //初始化c矩阵第一行和第一列为0 19 for (int i = 0; i < a.size(); i++) 20 { 21 c[i][0] = 0; 22 } 23 for (int j = 0; j < b.size(); j++) 24 { 25 c[0][j] = 0; 26 } 27 //然后动态规划计算LCS 28 for (int i = 1; i < a.size(); i++) 29 { 30 for (int j = 1; j < b.size(); j++) 31 { 32 if (a[i] == b[j]) 33 { 34 c[i][j] = c[i - 1][j - 1] + 1; 35 } 36 else 37 { 38 c[i][j] = MAX_VALUE(c[i - 1][j], c[i][j - 1]); 39 } 40 } 41 } 42 //打印c[i][j] 43 for (int i = 0; i < a.size(); i++) 44 { 45 for (int j = 0; j < b.size(); j++) 46 { 47 cout << c[i][j] << " "; 48 } 49 cout << endl; 50 } 51 52 return c[a.size() - 1][b.size() - 1]; 53 } 54 55 int main() 56 { 57 string a, b; 58 while (cin >> a >> b) 59 { 60 memset(c, 0, sizeof(a)); 61 cout << "max longest sequence:" << LCS(a, b) << endl; 62 } 63 }
输出结果:
如果需要输出公共子序列该怎么做?考虑到可能满足最长公共子序列长度的子序列不止一个,比如上图中的结果,有三个(3个4),如何才能打印出所有的公共子序列呢?
我们再回顾一下矩阵C,这里我加上了字符串a,b进行对应(见下图),画黄圈的地方是:字符串a中与字符串b中相同字符的地方,a与b的最大公共子序列的长度为4意味着图中四个画黄圈的字符组成这个公共子序列,并且这四个黄圈中的数字(c[i][j])依次是1 2 3 4,并且按照顺序,在一个最长公共子序列中,数字为2的黄圈一定位于数字为1的黄圈右下方,这样数下来,就有三个最长公共子序列分别是:BCBA BDAB BCAB
下面的程序,实现了这一功能: