子序列
在数学中,某个序列的子序列是从最初序列通过去除某些元素但不破坏余下元素的相对位置(在前或在后)而形成的新序列。比如:ABCDEFGHI的一个子序列是ADFGI。
最长公共子序列
理解了子序列就能简单的理解最长公共子序列。就是对于两个序列,有一个序列三满足既是序列1的子序列也是序列2的子序列而且是满足这个条件的子序列中最长的一个,它就是序列1,2的最长公共子序列。
比如:BDCABA和ABCBDAB的最长公共子序列就是BCBA。
实现
最长公共子序列(Longest Common Subsequence, LCS)是一道非常经典的动态规划题,要学这个最好先对动态规划有一定了解。
设: str1=a0a1...an , str2=b0b1...bm ,它们的最长公共子序列为 str=c0c1...ck
我们来看看怎么推出状态转移方程
首先,不难得出如下结论:
1. 如果
an=bm
,那么
ck=an=bm
且
c0...ck−1
是
a0...an−1
和
b0...bm−1
的最长公共子序列。(感觉运用了分治的思想)
2. 如果
an
!=
bm
,那么
c0...ck
是MAX(LCS(
a0...an−1
,
b0...bm
),LCS(
a0...an
,
b0...bm−1
))那一个的最长公共子序列。
由上述结论,我们设dp[i][j]为
a0...ai
和
b0...bj
的最长公共子序列的长度。所以:
以BDCABA和ABCBDAB为例,我们看看它的递推过程
代码模板
int LCS(char *str1, char *str2)
{
int len1=strlen(str1);
int len2=strlen(str2);
memset(dp,0,sizeof(dp);
for(int i=1;i<=len1;i++)
{
for(int j=1;j<=len2;j++)
{
if(str1[i-1]==str2[j-1]) dp[i][j] = dp[i-1][j-1]+1;
else dp[i][j] = max(dp[i-1][j],dp[i][j-1]);
}
}
return dp[len1][len2];
}
例题
给出两个字符串,求出其最长公共子序列及其长度。
代码:
#include <bits/stdc++.h>
using namespace std;
#define time_ (printf("%.6f\n", double(clock())/CLOCKS_PER_SEC))
typedef long long ll;
const int maxn = 1e3+5;
int dp[maxn][maxn];
//记录dp[i][j]是从哪递推过来的
//-1是dp[i-1][j-1],0是dp[i][j-1],1是dp[i-1][j]
int st[maxn][maxn];
char str1[maxn],str2[maxn];
void PrintLCS(int i,int j)
{
if(i==0 || j==0) return;
if(st[i][j]==-1)
{
PrintLCS(i-1,j-1);
printf("%c",str1[i-1]);
}
else if(st[i][j]==0)
{
PrintLCS(i,j-1);
}
else
{
PrintLCS(i-1,j);
}
}
void LCS()
{
int len1=strlen(str1);
int len2=strlen(str2);
memset(dp,0,sizeof(dp));
for(int i=1;i<=len1;i++)
{
for(int j=1;j<=len2;j++)
{
if(str1[i-1]==str2[j-1]) //这里是因为dp下标是从1开始,str1和str2的下标是从0开始
{
dp[i][j] = dp[i-1][j-1]+1;
st[i][j] = -1;
}
else if(dp[i][j-1]>dp[i-1][j])
{
dp[i][j] = dp[i][j-1];
st[i][j] = 0;
}
else
{
dp[i][j] = dp[i-1][j];
st[i][j] = 1;
}
}
}
printf("%d\n",dp[len1][len2]);
PrintLCS(len1,len2);
printf("\n");
}
int main()
{
gets(str1);
gets(str2);
LCS();
return 0;
}
这里为什么是BDAB而不是我上面递推图中的BCBA,大家可以看我代码思考一下,应该很容易思考出来。
其实是else if(dp[i][j-1]>dp[i-1][j])这一句的问题。