给定两个字符串,求最长公共子序列及其长度(不同于最长公共子串的是公共子串要求连续)
思路:
设字串为a,b 建立二维数组c用于打表,建立二维数组d,用于记录路径,其中c[i][j]表示的是a的前i个字符构成的字串和b的前j个字符构成的字串的最大公共字串的和,则分析题目得状态转移方程:若a[i]==b[j],有c[i][j]=1+c[i-1][j-1],记d[i][j]=0,如果a[i]!=b[j]有c[i][j]=max(c[i][j-1],c[i-1][j]),记d[i][j]=1或2(0<i<=strlen(a),0<j<=strlen(b))且对于任意i,j有边界条件c[0][j]=c[i][0]=0,两个数组遍历结束c[strlen(a)][strlen(b)]为所求的最大公共子串的长度。
接下来通过d来回溯具体的公共子串:i,j初始值为strlen(a),strlen(b),首先建立栈s用于逆序输出,由上述分析得,如d[i][j]=0,则a[i-1](或b[j-1])为最长公共子序列中的元素(-1是因为二维数组d和c下标都是以1开始的),压入栈中,若d[i][j]!=0,则以a[i],b[j]结尾的最大公共子串=以a[i-1],b[j]或a[i],b[j-1]结尾的最大公共子串,i或j自减,i或j<1时跳出循环,输出栈中元素即为所求,代码如下:
void maxlcs(char a[],char b[]){
int c[maxn][maxn];//用于求最大子串长度
int d[maxn][maxn];//用于回溯
int i,j;
for(i=0;i<=strlen(a);++i){//核心部分
c[0][i]=0;
}
for(j=0;j<=strlen(b);++j){
c[j][0]=0;
}
for(i=1;i<=strlen(a);++i)
for(j=1;j<=strlen(b);++j)
{
if(a[i-1]==b[j-1])
{c[i][j]=c[i-1][j-1]+1;
d[i][j]=0;
}
else {
if(c[i][j-1]>c[i-1][j])
{c[i][j]=c[i][j-1];
d[i][j]=1;
}
else {
c[i][j]=c[i-1][j];
d[i][j]=2;
}
}
}
i=strlen(a);
j=strlen(b);
while(i>=1&&j>=1){
switch(d[i][j]){
case 0:
s.push(a[i-1]);
i--;
j--;
break;
case 1:
j--;
break;
case 2:
i--;
break;
}
}
i=-1;
while(!s.empty()){
cout<<s.top()<<" ";
s.pop();
}
cout<<c[strlen(a)][strlen(b)];
}
注:1.如果只需求最大公共子序列的长度,由于c[i][j]只取决于它左边,上边和左上的元素,则i-1行之前已经不再需要,因此可以建立一个两行的数组来实现(滚动数组)
2.如果c[i][j-1]==c[i-1][j],则最长公共子序列可能有多个,修改d的范围,d[i][j]=3时,c[i][j-1]==c[i-1][j]回溯的时候可以用dfs求出多个路径。