题目链接:http://poj.org/problem?id=1458

题目大意:给出两个字符串,求出这样的一个最长的公共子序列的长度:子序列中的每个字符都能在两个原串中找到,而且每个字符的先后顺序和原串中的先后顺序一致。

输入有若干行,每行是两个字符串。对每一行输入的两个字符串,输出最长公共子串的长度。

Sample Input
abcfbc abfcab
programming contest
abcd mnp

Sample Output
4
2
0

算法分析

参考1:北大郭炜老师mooc课程
参考2:http://blog.csdn.net/u013480600/article/details/40741333

参考3:http://blog.csdn.net/lz161530245/article/details/76943991

输入两个串s1,s2,
设MaxLen(i,j)表示:s1的左边i个字符形成的子串,与s2左边的j个字符形成的子串的最长公共子序列的长度(i,j从0开始算)
MaxLen(i,j) 就是本题的“状态”
假定 len1 = strlen(s1),len2 = strlen(s2)
那么题目就是要求 MaxLen(len1,len2)

显然:
MaxLen(n,0) = 0 ( n= 0…len1)
MaxLen(0,n) = 0 ( n=0…len2)
递推公式:
if(s1[i-1] == s2[j-1]) //s1的最左边字符是s1[0]
    MaxLen(i,j) = MaxLen(i-1,j-1) + 1;
else
    MaxLen(i,j) = Max(MaxLen(i,j-1),MaxLen(i-1,j) );
时间复杂度O(mn),其中m,n是两个字串长度。

关于证明,可以阅读参考2参考3的证明过程。大概过程记录如下:

我们用Ax表示序列A的连续前x项构成的子序列,即Ax= a1,a2,……ax, By= b1,b2,……by, 我们用LCS(x, y)表示它们的最长公共子序列长度,那原问题等价于求LCS(m,n)。为了方便我们用L(x, y)表示Ax和By的一个最长公共子序列。
让我们来看看如何求LCS(x, y)。我们令x表示子序列,考虑最后一项 第()种情况:Ax = By
那么它们L(Ax, By)的最后一项一定是这个元素!
为什么呢?为了方便,我们令t=Ax=By, 我们用反证法:假设L(x,y)最后一项不是t,
则要么L(x,y)为空序列(别忘了这个),要么L(x,y)的最后一项是Aa=Bb ≠ t, 且显然有a<x,b<y。无论是哪种情况我们都可以把t接到这个L(x,y)后面,从而得到一个更长的公共子序列。矛盾!
如果我们从序列Ax中删掉最后一项ax得到Ax-,从序列By中也删掉最后一项by得到By-,(多说一句角标为0时,认为子序列是空序列),则我们从L(x,y)也删掉最后一项t得到的序列是L(x – , y - )。为什么呢?和上面的道理相同,如果得到的序列不是L(x - , y - ),则它一定比L(x - , y - )短,那么它后面接上元素t得到的子序列L(x,y)也比L(x - , y - )接上元素t得到的子序列短,这与L(x, y)是最长公共子序列矛盾。
因此L(x,y)=L(x-,y-)最后接上元素t,也就是说:
LCS(Ax, By) = LCS(x - , y - ) + 第()种情况:Ax ≠ By
仍然设t=L(Ax,By)的最后一个字符,或者L(Ax,By)是空序列(这时t是未定义值不等于任何值)。
则t≠Ax和t≠By至少有一个成立,因为t不能同时等于两个不同的值嘛!
(2.1) 如果t≠Ax,则有L(x,y)=L(x-,y),因为根本没Ax的事嘛。
也就是说:LCS(x,y) = LCS(x – , y)
(2.2) 如果t≠By,同理有L(x,y)= L(x,y-)。
也就是说:LCS(x,y) = LCS(x, y – )
可是,我们事先并不知道t,由定义,我们取最大的一个,因此这种情况下,有LCS(x,y)=max(LCS(x–,y),LCS(x,y–))。 看看目前我们已经得到了什么结论:
LCS(x,y) =
() LCS(x - ,y - ) + 如果Ax = By
() max(LCS(x – , y) , LCS(x, y – )) 如果Ax ≠ By
这是一个显然的递推式,光有递推可不行,初值是什么呢?
显然,一个空序列和任何序列的最长公共子序列都是空序列!所以我们有:
LCS(x,y) =
() LCS(x - ,y - ) + 如果Ax = By
() max(LCS(x – , y) , LCS(x, y – )) 如果Ax ≠ By
() 如果x=0或者y= 到此我们求出了计算最长公共子序列长度的递推公式。我们实际上计算了一个(n + )行(m + )列的表格(行是0..n,列是0..m),也就这个二维度数组LCS(n,m)。

证明过程

 #include <iostream>
#include <cstring>
using namespace std;
char sz1[];
char sz2[];
int maxLen[][];
int main()
{
while( cin >> sz1 >> sz2 )
{
int length1 = strlen( sz1);
int length2 = strlen( sz2);
int nTmp;
int i,j;
for( i = ;i <= length1; i ++ ) maxLen[i][] = ;
for( j = ;j <= length2; j ++ ) maxLen[][j] = ;
for( i = ;i <= length1;i ++ )
{
for( j = ; j <= length2; j ++ )
{
if( sz1[i-] == sz2[j-] )
maxLen[i][j] = maxLen[i-][j-] + ;
else
maxLen[i][j] = max(maxLen[i][j-],maxLen[i-][j]);
}
}
cout << maxLen[length1][length2] << endl;
}
return ;
}

上面的题目并没有要求输出最长的公共子序列。假如要输出最长公共子序列,可以阅读参考3的代码:(也可以暂时跳过,本文末尾有代码实现。)

 #include <stdio.h>
#include <string.h>
#include <stdlib.h>
int LCSLength(char* str1, char* str2, int **b)
{
int i,j,length1,length2,len;
length1 = strlen(str1);
length2 = strlen(str2); //双指针的方法申请动态二维数组
int **c = new int*[length1+]; //共有length1+1行
for(i = ; i < length1+; i++)
c[i] = new int[length2+];//共有length2+1列 for(i = ; i < length1+; i++)
c[i][]=; //第0列都初始化为0
for(j = ; j < length2+; j++)
c[][j]=; //第0行都初始化为0 for(i = ; i < length1+; i++)
{
for(j = ; j < length2+; j++)
{
if(str1[i-]==str2[j-])//由于c[][]的0行0列没有使用,c[][]的第i行元素对应str1的第i-1个元素
{
c[i][j]=c[i-][j-]+;
b[i][j]=; //输出公共子串时的搜索方向
}
else if(c[i-][j]>c[i][j-])
{
c[i][j]=c[i-][j];
b[i][j]=;
}
else
{
c[i][j]=c[i][j-];
b[i][j]=-;
}
}
}
/*
for(i= 0; i < length1+1; i++)
{
for(j = 0; j < length2+1; j++)
printf("%d ",c[i][j]);
printf("\n");
}
*/
len=c[length1][length2];
for(i = ; i < length1+; i++) //释放动态申请的二维数组
delete[] c[i];
delete[] c;
return len;
}
void PrintLCS(int **b, char *str1, int i, int j)
{
if(i== || j==)
return ;
if(b[i][j]==)
{
PrintLCS(b, str1, i-, j-);//从后面开始递归,所以要先递归到子串的前面,然后从前往后开始输出子串
printf("%c",str1[i-]);//c[][]的第i行元素对应str1的第i-1个元素
}
else if(b[i][j]==)
PrintLCS(b, str1, i-, j);
else
PrintLCS(b, str1, i, j-);
} int main(void)
{
char str1[],str2[];
int i,length1,length2,len;
printf("请输入第一个字符串:");
gets(str1);
printf("请输入第二个字符串:");
gets(str2);
length1 = strlen(str1);
length2 = strlen(str2);
//双指针的方法申请动态二维数组
int **b = new int*[length1+];
for(i= ; i < length1+; i++)
b[i] = new int[length2+];
len=LCSLength(str1,str2,b);
printf("最长公共子序列的长度为:%d\n",len);
printf("最长公共子序列为:");
PrintLCS(b,str1,length1,length2);
printf("\n");
for(i = ; i < length1+; i++)//释放动态申请的二维数组
delete[] b[i];
delete[] b;
system("pause");
return ;
}

求最长公共子序列长度并输出最长公共子序列

查找并输出最长公共子序列也可以参考https://wenku.baidu.com/view/7e96c94f2b160b4e767fcfc9.html

空间上的优化:

观察上面算法中的关键代码:

for( i = ;i <= length1;i ++ )
{
for( j = ; j <= length2; j ++ )
{
if( sz1[i-] == sz2[j-] ) maxLen[i][j] = maxLen[i-][j-] + ;
else maxLen[i][j] = max(maxLen[i][j-],maxLen[i-][j]);
}
}

可以发现,计算maxLen数组第i行时用到的只有第i行与第i-1行。我们的目的是要计算maxLen[length1][length2],所以,可以考虑只保存两行即可,也就是使用滚动数组只保存两行。

代码如下:(参考来源

cur表示当前需要求的那一行的下标。

 #include <iostream>
#include <cstring>
using namespace std;
char sz1[];
char sz2[];
int maxLen[][];
int main()
{
int i,j,length1,length2,cur=; while( cin >> sz1 >> sz2 )
{
length1 = strlen( sz1);
length2 = strlen( sz2);
for( i=;i<; i++ ) maxLen[i][]=;
for( j=;j<=length2;j++ ) maxLen[][j]=;
cur=; for( i = ;i <= length1;i ++ )
{
cur ^= ;
for( j = ; j <= length2; j ++ )
{
if( sz1[i-] == sz2[j-] )
maxLen[cur][j] = maxLen[cur^][j-] + ;
else
maxLen[cur][j] = max(maxLen[cur][j-],maxLen[cur^][j]);
}
}
cout << maxLen[cur][length2] << endl;
}
return ;
}

下面修改一下代码寻找出一个最长公共子序列。

上面经过空间优化后,也只是寻找到了最长公共子序列的长度,那么如何得到一个最长公共子序列而仅仅不是简单的长度呢?其实我们离真正的答案只有一步之遥!

参考上图,我们建立一个二维数组ans[][],在寻找最长公共子序列的长度时用ans[i][j]记录LCS(i,j)是如何来的(从左边、上边或是从左上),ans[i][j]等于1,2,3分别表示:

L(x,y) = L(x, y – 1)

L(x,y)= L(x – 1, y)

L(x,y) = L(x,- 1 y- 1)末尾接上Ax

当ans[i][j]等于3时字符串1的第i个字符(或字符串2的第j个字符,其实两者相同)肯定是最长公共子序列的一部分,要保留到temp[ ]中。所以从ans[][]右下角逆推即可求出temp[ ],然后逆序输出temp[]即可。代码如下:

 //51Nod动态规划教程例题 求最长公共子序列的长度并输出一个最长公共子序列
#include <iostream>
#include <cstring>
using namespace std;
#define maxN 5005
char sz1[maxN];
char sz2[maxN];
int maxLen[][maxN];
char ans[maxN][maxN]={}; void printLCS(int len1,int len2);//输出一个最长公共子序列
int main()
{
int i,j,length1,length2,cur=;
freopen("poj1458.in","r",stdin);
while( cin >> sz1 >> sz2 )
{
memset(ans,,sizeof(char)*maxN*maxN);
length1 = strlen( sz1);
length2 = strlen( sz2);
for( i=;i<; i++ ) maxLen[i][]=;
for( j=;j<=length2;j++ ) maxLen[][j]=;
cur=; for( i = ;i <= length1;i ++ )
{
cur ^= ;
for( j = ; j <= length2; j ++ )
{
if( sz1[i-] == sz2[j-] )
{
maxLen[cur][j] = maxLen[cur^][j-] + ;
ans[i][j]=;
}
else
{
//maxLen[cur][j] = max(maxLen[cur][j-1],maxLen[cur^1][j]);
if(maxLen[cur][j-]>maxLen[cur^][j])
{
maxLen[cur][j]=maxLen[cur][j-];
ans[i][j]=;
}
else
{
maxLen[cur][j]=maxLen[cur^][j];
ans[i][j]=;
}
}
}
}
cout << maxLen[cur][length2] << endl;
if(maxLen[cur][length2]>) printLCS(length1,length2);
}
return ;
}
void printLCS(int len1,int len2)//输出一个最长公共子序列
{
char temp[maxN];
int i=len1,j=len2,k=;
while(ans[i][j]!=)
{
if(ans[i][j]==) { temp[k++]=sz1[i-]; i--;j--; }
else if(ans[i][j]==)
{
j--;
}
else if(ans[i][j]==)
{
i--;
}
}
for(k--;k>=;k--) printf("%c",temp[k]);
printf("\n");
}

最长公共子序列(POJ1458)的更多相关文章

  1. 最长公共子序列poj1458

    #include<map> #include<set> #include<list> #include<cmath> #include<queue ...

  2. 最长公共子序列LCS(POJ1458)

    转载自:https://www.cnblogs.com/huashanqingzhu/p/7423745.html 题目链接:http://poj.org/problem?id=1458 题目大意:给 ...

  3. POJ-1458(LCS:最长公共子序列模板题)

    Common Subsequence POJ-1458 //最长公共子序列问题 #include<iostream> #include<algorithm> #include& ...

  4. poj1458 求最长公共子序列 经典DP

    Common Subsequence Time Limit: 1000MS   Memory Limit: 10000K Total Submissions: 45763   Accepted: 18 ...

  5. POJ-1458.CommonSubsequence.(DP:最长公共子序列裸题)

    本题大意:给出两个字符串,让你求出最长公共子序列的长度并输出. 本题思路:本题是经典的DP问题,由于是两个字符串,那么我们就用一个二维数组来进行区分,用dp[ i ][ j ]来表示在s1和s2中分别 ...

  6. POJ-1458 Common Subsequence(线性动规,最长公共子序列问题)

    Common Subsequence Time Limit: 1000MS Memory Limit: 10000K Total Submissions: 44464 Accepted: 18186 ...

  7. POJ1458 最长公共子序列

    描述: 给定两个字符串,求其最长公共子序列(不用连续), 输入: abc bcc programning content 输出: 2 2 解法: 动态规划. 定义dp[i][j]表示s1到i索引,以及 ...

  8. POJ 1458 Common Subsequence(最长公共子序列LCS)

    POJ1458 Common Subsequence(最长公共子序列LCS) http://poj.org/problem?id=1458 题意: 给你两个字符串, 要你求出两个字符串的最长公共子序列 ...

  9. 用python实现最长公共子序列算法(找到所有最长公共子串)

    软件安全的一个小实验,正好复习一下LCS的写法. 实现LCS的算法和算法导论上的方式基本一致,都是先建好两个表,一个存储在(i,j)处当前最长公共子序列长度,另一个存储在(i,j)处的回溯方向. 相对 ...

随机推荐

  1. 使用PHP生成二维码图像

    1.PHP生成二维码图像的类QRcode http://www.phper.org.cn/?post=128 QRcode是用于生成二维条形码的开放源码 (LGPL) 库.提供 API 创建条码图像. ...

  2. 奇怪吸引子---AnishchenkoAstakhov

    奇怪吸引子是混沌学的重要组成理论,用于演化过程的终极状态,具有如下特征:终极性.稳定性.吸引性.吸引子是一个数学概念,描写运动的收敛类型.它是指这样的一个集合,当时间趋于无穷大时,在任何一个有界集上出 ...

  3. C#中的集合(HashTable与Array类)【转】

    一.Array类 1.Array类的属性 序号 属性 & 描述 1 IsFixedSize 获取一个值,该值指示数组是否带有固定大小. 2 IsReadOnly 获取一个值,该值指示数组是否只 ...

  4. js中document.write的那点事

    document.write()方法可以用在两个方面:页面载入过程中用实时脚本创建页面内容,以及用延时脚本创建本窗口或新窗口的内容.该方法需要一个字符串参数,它是写到窗口或框架中的HTML内容.这些字 ...

  5. "Your computer could not be joined to the domain. You have exceeded the maximum number of computer accounts you are allowed to create in this domain. Contact your system administrator to have this limit reset or increased."

    用一个普通的域帐号玩私有云的时候,遇到了如下的报错. "Your computer could not be joined to the domain. You have exceeded ...

  6. 详说 Block Formatting Contexts (块级格式化上下文)

    在上文<详说清除浮动>中,Kayo 较为详细地介绍了 BFC ,也就是本文的主角 Block Formatting Contexts (块级格式化上下文),本文会基于上文关于 BFC 的部 ...

  7. 查看LINQ Expression編譯後的SQL語法(转)

    在用了LINQ語法之後的一個月,我幾乎把SQL語法全部拋到腦後了,不過 LINQ好用歸好用,但是實際上操作資料庫的還是SQL語法,如果不知道LINQ語法 編譯過後產生怎樣的SQL語法,一不小心效能就會 ...

  8. 解决ThinkPHP的Create方法失效而没有提示错误信息的问题

    ThinkPHP中的数据创建Create方法是一个非常有用的功能,它自动根据表单数据创建数据对象(在表字段很多的情况下尤其明显) 但有时候该方法可能并未按照你期望的来工作,比如方法不工作而且还没有提示 ...

  9. IIS 7.5: HOW TO ENABLE TLS 1.1 AND TLS 1.2

    In IIS 7.5, which is installed on Windows 2008 R2 servers, only SSL 3.0 and TLS 1.0 are enabled for ...

  10. servlet种下cookie后如何携带cookie继续往下走

    事情是这样的,今天我在应用1里面手动种下了一个cookie,然后它会发接着访问应用2,因为是我手动setCookie,所以它还没来得及携带cookie继续前往下一站,于是,apple pen,炸了. ...