题目链接: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表示子序列,考虑最后一项第(1)种情况: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-1,从序列By中也删掉最后一项by得到By-1,(多说一句角标为0时,认为子序列是空序列),则我们从L(x,y)也删掉最后一项t得到的序列是L(x – 1, y - 1)。为什么呢?和上面的道理相同,如果得到的序列不是L(x - 1, y - 1),则它一定比L(x - 1, y - 1)短,那么它后面接上元素t得到的子序列L(x,y)也比L(x - 1, y - 1)接上元素t得到的子序列短,这与L(x, y)是最长公共子序列矛盾。
因此L(x,y)=L(x-1,y-1)最后接上元素t,也就是说:
LCS(Ax, By) = LCS(x - 1, y - 1) + 1第(2)种情况:Ax ≠ By
仍然设t=L(Ax,By)的最后一个字符,或者L(Ax,By)是空序列(这时t是未定义值不等于任何值)。
则t≠Ax和t≠By至少有一个成立,因为t不能同时等于两个不同的值嘛!
(2.1) 如果t≠Ax,则有L(x,y)=L(x-1,y),因为根本没Ax的事嘛。也就是说:LCS(x,y) = LCS(x – 1, y)
(2.2) 如果t≠By,同理有L(x,y)= L(x,y-1)。也就是说:LCS(x,y) = LCS(x, y – 1)
可是,我们事先并不知道t,由定义,我们取最大的一个,因此这种情况下,有LCS(x,y)=max(LCS(x–1,y),LCS(x,y–1))。看看目前我们已经得到了什么结论:
LCS(x,y) = (1) LCS(x - 1,y - 1) + 1 如果Ax = By(2) max(LCS(x – 1, y) , LCS(x, y – 1)) 如果Ax ≠ By
这是一个显然的递推式,光有递推可不行,初值是什么呢?
显然,一个空序列和任何序列的最长公共子序列都是空序列!所以我们有:
LCS(x,y) = (1) LCS(x - 1,y - 1) + 1     如果Ax = By(2) max(LCS(x – 1, y) , LCS(x, y – 1))     如果Ax ≠ By(3) 0     如果x=0或者y=0到此我们求出了计算最长公共子序列长度的递推公式。我们实际上计算了一个(n + 1)行(m + 1)列的表格(行是0..n,列是0..m),也就这个二维度数组LCS(n,m)。

证明过程

 1 #include <iostream>
 2 #include <cstring>
 3 using namespace std;
 4 char sz1[5005];
 5 char sz2[5005];
 6 int maxLen[5005][5005];
 7 int main()
 8 {
 9     while( cin >> sz1 >> sz2 )
10     {
11         int length1 = strlen( sz1);
12         int length2 = strlen( sz2);
13         int nTmp;
14         int i,j;
15         for( i = 0;i <= length1; i ++ ) maxLen[i][0] = 0;
16         for( j = 0;j <= length2; j ++ ) maxLen[0][j] = 0;
17         for( i = 1;i <= length1;i ++ )
18         {
19             for( j = 1; j <= length2; j ++ )
20             {
21                 if( sz1[i-1] == sz2[j-1] )
22                     maxLen[i][j] = maxLen[i-1][j-1] + 1;
23                 else
24                     maxLen[i][j] = max(maxLen[i][j-1],maxLen[i-1][j]);
25             }
26         }
27         cout << maxLen[length1][length2] << endl;
28     }
29     return 0;
30 }

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

 1 #include <stdio.h>
 2 #include <string.h>
 3 #include <stdlib.h>
 4 int LCSLength(char* str1, char* str2, int **b)
 5 {
 6     int i,j,length1,length2,len;
 7     length1 = strlen(str1);
 8     length2 = strlen(str2);
 9
10     //双指针的方法申请动态二维数组
11     int **c = new int*[length1+1]; //共有length1+1行
12     for(i = 0; i < length1+1; i++)
13         c[i] = new int[length2+1];//共有length2+1列
14
15     for(i = 0; i < length1+1; i++)
16         c[i][0]=0;        //第0列都初始化为0
17     for(j = 0; j < length2+1; j++)
18         c[0][j]=0;        //第0行都初始化为0
19
20     for(i = 1; i < length1+1; i++)
21     {
22         for(j = 1; j < length2+1; j++)
23         {
24             if(str1[i-1]==str2[j-1])//由于c[][]的0行0列没有使用,c[][]的第i行元素对应str1的第i-1个元素
25             {
26                 c[i][j]=c[i-1][j-1]+1;
27                 b[i][j]=0;          //输出公共子串时的搜索方向
28             }
29             else if(c[i-1][j]>c[i][j-1])
30             {
31                 c[i][j]=c[i-1][j];
32                 b[i][j]=1;
33             }
34             else
35             {
36                 c[i][j]=c[i][j-1];
37                 b[i][j]=-1;
38             }
39         }
40     }
41     /*
42     for(i= 0; i < length1+1; i++)
43     {
44     for(j = 0; j < length2+1; j++)
45     printf("%d ",c[i][j]);
46     printf("\n");
47     }
48     */
49     len=c[length1][length2];
50     for(i = 0; i < length1+1; i++)    //释放动态申请的二维数组
51         delete[] c[i];
52     delete[] c;
53     return len;
54 }
55 void PrintLCS(int **b, char *str1, int i, int j)
56 {
57     if(i==0 || j==0)
58         return ;
59     if(b[i][j]==0)
60     {
61         PrintLCS(b, str1, i-1, j-1);//从后面开始递归,所以要先递归到子串的前面,然后从前往后开始输出子串
62         printf("%c",str1[i-1]);//c[][]的第i行元素对应str1的第i-1个元素
63     }
64     else if(b[i][j]==1)
65         PrintLCS(b, str1, i-1, j);
66     else
67         PrintLCS(b, str1, i, j-1);
68 }
69
70 int main(void)
71 {
72     char str1[100],str2[100];
73     int i,length1,length2,len;
74     printf("请输入第一个字符串:");
75     gets(str1);
76     printf("请输入第二个字符串:");
77     gets(str2);
78     length1 = strlen(str1);
79     length2 = strlen(str2);
80     //双指针的方法申请动态二维数组
81     int **b = new int*[length1+1];
82     for(i= 0; i < length1+1; i++)
83         b[i] = new int[length2+1];
84     len=LCSLength(str1,str2,b);
85     printf("最长公共子序列的长度为:%d\n",len);
86     printf("最长公共子序列为:");
87     PrintLCS(b,str1,length1,length2);
88     printf("\n");
89     for(i = 0; i < length1+1; i++)//释放动态申请的二维数组
90         delete[] b[i];
91     delete[] b;
92     system("pause");
93     return 0;
94 }

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

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

空间上的优化:

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

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

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

代码如下:(参考来源)

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

 1 #include <iostream>
 2 #include <cstring>
 3 using namespace std;
 4 char sz1[5005];
 5 char sz2[5005];
 6 int maxLen[2][5005];
 7 int main()
 8 {
 9     int i,j,length1,length2,cur=0;
10
11     while( cin >> sz1 >> sz2 )
12     {
13         length1 = strlen( sz1);
14         length2 = strlen( sz2);
15         for( i=0;i<2; i++ ) maxLen[i][0]=0;
16         for( j=0;j<=length2;j++ ) maxLen[0][j]=0;
17         cur=0;
18
19         for( i = 1;i <= length1;i ++ )
20         {
21             cur ^= 1;
22             for( j = 1; j <= length2; j ++ )
23             {
24                 if( sz1[i-1] == sz2[j-1] )
25                     maxLen[cur][j] = maxLen[cur^1][j-1] + 1;
26                 else
27                     maxLen[cur][j] = max(maxLen[cur][j-1],maxLen[cur^1][j]);
28             }
29         }
30         cout << maxLen[cur][length2] << endl;
31     }
32     return 0;
33 }

View Code

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

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

参考上图,我们建立一个二维数组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[]即可。代码如下:

 1 //51Nod动态规划教程例题 求最长公共子序列的长度并输出一个最长公共子序列
 2 #include <iostream>
 3 #include <cstring>
 4 using namespace std;
 5 #define maxN 5005
 6 char sz1[maxN];
 7 char sz2[maxN];
 8 int maxLen[2][maxN];
 9 char ans[maxN][maxN]={0};
10
11 void printLCS(int len1,int len2);//输出一个最长公共子序列
12 int main()
13 {
14     int i,j,length1,length2,cur=0;
15     freopen("poj1458.in","r",stdin);
16     while( cin >> sz1 >> sz2 )
17     {
18         memset(ans,0,sizeof(char)*maxN*maxN);
19         length1 = strlen( sz1);
20         length2 = strlen( sz2);
21         for( i=0;i<2; i++ ) maxLen[i][0]=0;
22         for( j=0;j<=length2;j++ ) maxLen[0][j]=0;
23         cur=0;
24
25         for( i = 1;i <= length1;i ++ )
26         {
27             cur ^= 1;
28             for( j = 1; j <= length2; j ++ )
29             {
30                 if( sz1[i-1] == sz2[j-1] )
31                 {
32                     maxLen[cur][j] = maxLen[cur^1][j-1] + 1;
33                     ans[i][j]=3;
34                 }
35                 else
36                 {
37                     //maxLen[cur][j] = max(maxLen[cur][j-1],maxLen[cur^1][j]);
38                     if(maxLen[cur][j-1]>maxLen[cur^1][j])
39                     {
40                         maxLen[cur][j]=maxLen[cur][j-1];
41                         ans[i][j]=1;
42                     }
43                     else
44                     {
45                         maxLen[cur][j]=maxLen[cur^1][j];
46                         ans[i][j]=2;
47                     }
48                 }
49             }
50         }
51         cout << maxLen[cur][length2] << endl;
52         if(maxLen[cur][length2]>0) printLCS(length1,length2);
53     }
54     return 0;
55 }
56 void printLCS(int len1,int len2)//输出一个最长公共子序列
57 {
58     char temp[maxN];
59     int i=len1,j=len2,k=0;
60     while(ans[i][j]!=0)
61     {
62         if(ans[i][j]==3) { temp[k++]=sz1[i-1]; i--;j--; }
63         else if(ans[i][j]==1)
64         {
65             j--;
66         }
67         else if(ans[i][j]==2)
68         {
69             i--;
70         }
71     }
72     for(k--;k>=0;k--) printf("%c",temp[k]);
73     printf("\n");
74 }

最长公共子序列(POJ1458)相关推荐

  1. [动态规划]最长公共子序列

    动态规划的本质 动态规划的实质就是:记忆化搜索. 对于要用动态规划进行解决的问题的特点: 问题具有最优子结构性质:如果问题的最优解包含的子问题的解也是最优的,就称该问题具有最优子结构. 问题具有子问题 ...

  2. Bailian2806 公共子序列【最长公共子序列+DP】

    2806:公共子序列 描述 我们称序列Z = < z1, z2, -, zk >是序列X = < x1, x2, -, xm >的子序列当且仅当存在 严格上升 的序列< ...

  3. 动态规划求解LCS最长公共子序列问题c++

    文章目录 求最长公共子序列 最长公共子序列问题 刻画最长公共子序列的特征 递归公式 C++代码 求最长公共子序列长度 打印任意一组最长公共子序列 模板题 求最长公共子序列 最长公共子序列问题 (Lon ...

  4. 最长公共子序列(LCS)问题 Longest Common Subsequence 与最长公告字串 longest common substr...

    问题描述:字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列.令给定的字符序列X="x0,x1,-,xm-1",序列Y=& ...

  5. 【动态规划】最长公共子序列与最长公共子串

    1. 问题描述 子串应该比较好理解,至于什么是子序列,这里给出一个例子:有两个母串 cnblogs belong 比如序列bo, bg, lg在母串cnblogs与belong中都出现过并且出现顺序与 ...

  6. POJ 3080 多个串最长公共子序列

    求多个串最长公共子序列,字典序最小输出.枚举剪枝+kmp.比较简单,我用find直接查找16ms #include<iostream> #include<string> #in ...

  7. java实现最长连续子序列_最长公共子序列 ||

    问题:在 前一篇文章 最长公共子序列 | 的基础上要求将所有的最长公共子序列打印出来,因为最长公共子序列可能不只一种. 难点:输出一个最长公共子序列并不难,难点在于输出所有的最长公共子序列,我们需要在 ...

  8. 动态规划—最长公共子序列问题 HDU-1159 Common Subsequence

    动态规划-最长公共子序列问题 Common Subsequence [ HDU - 1159 ] A subsequence of a given sequence is the given sequ ...

  9. 触类旁通,经典面试题最长公共子序列应该这么答

    作者 |  labuladong 来源 | labuladong(ID:labuladong) [导读]最长公共子序列(Longest Common Subsequence,简称 LCS)是一道非常经 ...

最新文章

  1. 李德毅院士:探索新一代人工智能产业发展
  2. html求和按钮,使用模板标记在html模板中求和
  3. Linux内存buffer和cache的区别
  4. Mongodb 基础 查询表达式
  5. 自然语言交流系统 phxnet团队 创新实训 项目博客 (五)
  6. 地摊叫卖、超市播音工具-简洁的文字转语音播音软件
  7. 游三大界后感(付照片)
  8. gff文件_根据gff/gtf等注释文件取负链上的序列:先反向互补染色体再截取?还是先截取区间再反向互补序列?...
  9. 基金份额净值估值是什么?
  10. Java 8 中的 Map 骚操作,学习下
  11. Linux7安装硬盘显示错误,【原创文章】centos7 badblocks检测硬盘出现Value too large for defined data type错误的原因和解决办法...
  12. vue loading组件
  13. HTTP请求的完全过程
  14. Expected property shorthand报错
  15. 《笑傲江湖》清心普善咒——曲谱(琴箫合奏曲)
  16. oracle安装后,电脑变得很卡,解决办法(安装的是oracle11g)
  17. 感谢一路相伴的朋友们!我的个人工作室招人啦!
  18. 欲先攻其事必先利其器 (第三方资源篇)
  19. Spring技术内幕
  20. win7 隐藏受保护的操作系统文件 消失

热门文章

  1. 黯然推荐:王江民先生悼文(文言)
  2. 在Delphi中获取和修改文件的时间
  3. 安装VxWorks 6.6有感
  4. 放弃深度学习?我承认是因为线性代数
  5. 2018年宝鸡市三检文科数学题目解答
  6. yii2 Rbac使用yii命令一键建表
  7. [网页设计]Ajax、Comet与Websocket--转
  8. 关于Toast 详解
  9. [轉]function, new function, new Function
  10. .net下操作XML的几篇文章(downmoon收集自MSDN)