1265:【例9.9】最长公共子序列
【题目描述】
一个给定序列的子序列是在该序列中删去若干元素后得到的序列。
确切地说,若给定序列X=<x1,x2,…,xm>,则另一序列Z=<z1,z2,…,zk>是X的子序列
是指存在一个严格递增的下标序列<i1,i2,…,ik>,使得对于所有j=1,2,…,k有:Xij=Zj
例如,序列Z=<B,C,D,B>是序列X=<A,B,C,B,D,A,B>的子序列,相应的递增下标序列为<2,3,5,7>。
给定两个序列X和Y,当另一序列Z既是X的子序列又是Y的子序列时,称Z是序列X和Y的公共子序列。
例如,若X=<A,B,C,B,D,A,B>和Y=<B,D,C,A,B,A>,则序列<B,C,A>是X和Y的一个公共子序列,
序列<B,C,B,A>也是X和Y的一个公共子序列。而且,后者是X和Y的一个最长公共子序列.因为X和Y没有长度大于4的公共子序列。
给定两个序列X=<x1,x2,…,xm>和Y=<y1,y2….yn>.要求找出X和Y的一个最长公共子序列。
【输入】
共有两行。每行为一个由大写字母构成的长度不超过1000的字符串,表示序列X和Y。
【输出】
第一行为一个非负整数。表示所求得的最长公共子序列的长度。若不存在公共子序列.则输出文件仅有一行输出一个整数0。
提示:
最长公共子串(Longest Common Substirng)和最长公共子序列(Longest Common
Subsequence, LCS)的区别为:子串是串的一个连续的部分,子序列则是不改变序列的顺序,
而从序列中去掉任意的元素而获得新的序列,也就是说,子串中字符的位置必须是连续的,
子序列则可以不必连续.字符串长度小于等于1000.
算法分析:
与最长不下降子序列(LIS)类似的,我们可以以子序列的结尾作为状态,但现在有两个子序列,
那么直接以两个子序列当前的结尾作为状态即可.
① 确定状态:
设F[x][y]表示S[l..x]与T[1..y]的最长公共子序列的长度,答案为F[|S|][|T|],
② 确定状态转移方程和边界条件:
分三种情况来考虑:
•S[x]不在公共于序列中:该情况下F[x][y] = F[x-l][y],
•T[y]不在公共于序列中:该情况下F[x][y] = F[x][y-l],
-S[x] = T[y],S[x]与 T[y]在公共子序列中:该情况下.FEx][y] = F[x-l][y-1]+ 1。
F[x][y]取上述三种情况的最大值。综上,
状态转移方程: F[x][y] = max{F[x—l][y],F[x][y—l],F[x—l][y—l] + l},其中第三种情况要満足S[x] = T[y];
边界条件:F[O][y]=O, F[x][O] = O.
③程序实现,
计算F[x][y]时用到F[x-l][y-l].F[x-l][y],FCx][y-l]这些状态,它们要么在
F[x][y]的上一行,要么在F[x][y]的左边。因此預处理出第。行,然后按照行从小到大、同一行
按照列从小到大的顺序来计算就可以用迭代法计算出来。时间复杂度为0(|S| * |T|).
借鉴网上一张图:
代码:
#include<iostream>
using namespace std;
//数据初始化
const int MAXN=5005;
string S,T;
int F[20][20];
//处理函数
//主函数
int main(){
//初始化数据
//输入数据
cin>>S;
cin>>T;
int ls=S.length();
int lt=T.length();
int n=0;
//事务处理
for(int i=1;i<=ls;i++){
for (int j = 1; j <=lt ; ++j) {
F[i][j]=max(F[i-1][j],F[i][j-1]);
n=F[i][j];
if(S[i-1]==T[j-1]){
F[i][j]=max(F[i][j],F[i-1][j-1]+1);
}
}
}
//输出数据
cout<<F[ls][lt]<<endl;
return 0;
}
总结:
1.这一题感觉有点难,代码看着比较少,但是理解起来有点抽象
2.这一题还是比较基础的一个题只要求出长度,进阶版是还要求打印出公共子序列。
参考: