子序列要求元素顺序一致就可以了,而字串必须是连续的。如ABCBDAB与BDCABA两个字符串,最长公共子序列有BCBA、BDAB和BCAB, 而最长公共字串只有AB和BD<连续>。当然这里的求解只求一个,但通常是这样直接说求最长公共子串,子序列,准确的应该是之一。
最长公共子序列
法一:穷举法
检查字符串x所有字序列,共有2^m个,检查它是否在y字符串中出现,每个需要O(n),时间复杂度为指数级的。
法二:动态规划(DP)
将两个字符串x[1…m]和y[1…n]放在x轴和y轴方向上便得到一个二维数组c[i,j]来记录x[1…i]和y[1…j]最长公共子序列个数。
当x[i]==y[j]的时候 c[i,j] = c[i-1,j-1]+1;当不相等的时候,c[i,j] = max{c[i-1, j], c[i,j-1]}.
采用自底向上的思想,这样时间复杂度就等于lcs独立子问题的个数O(mn),不然需要重复计算子问题,时间复杂度仍然为指数级的。
代码如下:
// 最长公共子序列(不连续) 需要有个标记数组用于回溯
void lcs_sequences(const char* str1, const char* str2, int len1, int len2)
{
int **c = new int*[len1+1];
int **b = new int*[len1+1];
int i, j;
for(i = 0; i < len1+1; i++)
{
c[i] = new int[len2+1];
b[i] = new int[len2+1];
}
for(i = 0; i <= len1; i ++)
for(j = 0; j <= len2; j++)
c[i][j] = 0;
for(i = 1; i <= len1; i++)
{
for(j = 1; j <= len2; j++)
{
if(str1[i-1] == str2[j-1])
{
c[i][j] = c[i-1][j-1] +1;
b[i][j] = 0; // 来自左上
}
else{
if(c[i-1][j] > c[i][j-1]){
c[i][j] = c[i-1][j];
b[i][j] = 1; // 来自上方
}
else{
c[i][j] = c[i][j-1]; // 来自左方
b[i][j] = 2; // 来自上方
}
}
}
}
cout << "最长公共子序列长度: " << c[len1][len2] << endl;
// 回溯求解路径
i = len1;
j = len2;
char *x = new char[c[len1][len2]];
int k = 0;
/*while(i > 0 && j > 0){
if(b[i][j] == 0) // 来自左上
{
x[k++] = str1[i-1];
//cout << str1[i-1];
i--;
j--;
}
else if(b[i][j] == 1) i--;
else j--;
}*/
// 不使用标记数组进行回溯 直接使用str1和str2及c[i][j]得出结果
while(i > 0 && j > 0)
{
if(str1[i-1] == str2[j-1])
{
x[k++] = str1[i-1];
i--;
j--;
}
else if(c[i][j] == c[i][j-1]) j--;
else i--;
}
cout << "the lcs_opt is: " ;
for(i = c[len1][len2]-1; i >= 0 ; i--)
{
cout << x[i];
}
cout << endl;
for(i= 0; i < len1; i++)
delete[] c[i];
delete []c;
delete []x;
}
上面注释的代码,我们用标记数组来跟踪来源,当然也可以不适用标记数字,直接使用c[i,j]与str1<