最长公共子序列是一个特别常见的动态规划问题,题目的意思是给定两个字符串X,Y,让你求一个字符串Z,使C中的每个字符即属于X,又属于Y,且字符在Z中的顺序必须和X,Y中的顺序是一样的,(这里区分子序列和子串的概念:子序列是不连续的,子串是连续的)举个例子X={A,B,C,B,D,A,B},Y={B.D.C.A.B.A},那么最长的公共序列Z={B,C,B,A}或{B,D,A,B},最朴素的算法就是遍历所有X的所有子序列,对于每个子序列检查是否是Y的子序列,这个方法太过暴力…光枚举X的子序列就要2^(X的长度),还要把每个字序列和Y的每个字序列比较,复杂度是O(2^(x+y的长度)),所以我们要找一个高效的算法。
首先我们分析这个问题的最优解是什么情况,令X={X1,X2,……,Xm},Y ={Y1,Y2,……,Yn},Z={Z1,Z2,……,Zk}为X和Y的任意LCS
1.如果Xm==Yn,那么Zk==Xm==Yn,且Zk-1是Xm-1和Yn-1的一个LCS
2.如果Xm!=Yn,那么Zk!=Xm意味着Zk是Xm-1和Yn的一个LCS
3.如果Xm!=Yn,那么Zk!=Yn,意味着Zk是Xm和Yn-1的一个LCS
所以两个序列的LCS包含两个序列的前缀的LCS,因此,LCS问题具有最优子结构的性质。而如果我们要求解X,Y的的LCS,我们就必须求解Xm-1和Yn-1的一个LCS,将Xm==Yn加入到这个LCS中,如果Xm!=Yn,那么我们就要求解Xm-1和Y的LCS的LCS或Yn-1和X的LCS,两个LCS取最长的,而这两个LCS的求解都需要求解Xm-1和Yn-1的LCS,所以我们要求解的问题具有重叠子问题的性质,所以这个问题符合动态规划的两个性质
我们这是就可以写出LCS为代码了
我们可以以Hdu的1159为例:
Common Subsequence
Problem Description
A subsequence of a given sequence is the given sequence with some elements (possible none) left out. Given a sequence X = < x1, x2, …, xm> another sequence Z = < z1, z2, …, zk> is a subsequence of X if there exists a strictly increasing sequence < i1, i2, …, ik> of indices of X such that for all j = 1,2,…,k, xij = zj. For example, Z = < a, b, f, c> is a subsequence of X = < a, b, c, f, b, c> with index sequence < 1, 2, 4, 6>. Given two sequences X and Y the problem is to find the length of the maximum-length common subsequence of X and Y.
The program input is from a text file. Each data set in the file contains two strings representing the given sequences. The sequences are separated by any number of white spaces. The input data are correct. For each set of data the program prints on the standard output the length of the maximum-length common subsequence from the beginning of a separate line.
Sample Input
abcfbc abfcab
programming contest
abcd mnp
Sample Output
4
2
0
题目就是标准的LCS问题,我们来看代码
#include<bits/stdc++.h>
using namespace std;
int dp[1005][1005];
int main()
{
ios::sync_with_stdio(0);
cin.tie(0);
char s1[1000],s2[1000];
while(cin>>s1+1>>s2+1) {
memset(dp,0,sizeof(dp));
int len1=strlen(s1),len2=strlen(s2),i,j,sum=0;
for(i=1;i<len1;i++){
for(j=1;j<len2;j++){
if(s1[i]==s2[j]) {
dp[i][j]=dp[i-1][j-1]+1;
}
else {
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
}
cout<<dp[i-1][j-1]<<endl;
}
return 0;
}
我们首先遍历S1的每一个元素,然后对每一个S2进行遍历,dp[i][j]中保存的就是到S1i和S2j的LCS,但我们看到我们每次都只用到dp[i]和dp[i-1]两行内容,这时我们可以在空间上进行优化,既然我们只用到这一行和上一行,那么我们只需要一个两行的数组,这里用到一个小技巧:^,1^0=1,1^1=0,这样对两行的选取进行优化,附上空间优化的代码:
#include<bits/stdc++.h>
using namespace std;
int dp[2][1005];
int main()
{
char s1[1000],s2[1000];
while(~scanf("%s%s",s1+1,s2+1)) {
memset(dp,0,sizeof(dp));
int len1=strlen(s1+1),len2=strlen(s2+1),i,j,flag=0;
for(i=1;i<=len1;i++){
for(j=1;j<=len2;j++){
if(s1[i]==s2[j]) {
dp[flag][j]=dp[flag^1][j-1]+1;
}
else {
dp[flag][j]=max(dp[flag^1][j],dp[flag][j-1]);
}
}
flag^=1;
}
cout<<dp[flag^1][j-1]<<endl;
}
return 0;
}
这时LCS的时间复杂度O(nm),空间复杂度O(4*max(n,m))已经有课非常大的优化,当然对于不同的问题可能还有其他剪枝的优化,这里就要看你的本事了