题目链接: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的证明过程。大概过程记录如下:

  1. 我们用Ax表示序列A的连续前x项构成的子序列,即Ax= a1,a2,……ax, By= b1,b2,……by, 我们用LCS(x, y)表示它们的最长公共子序列长度,那原问题等价于求LCS(m,n)。为了方便我们用L(x, y)表示AxBy的一个最长公共子序列。
  2. 让我们来看看如何求LCS(x, y)。我们令x表示子序列,考虑最后一项
  3.  
  4. 第()种情况:Ax By
  5. 那么它们L(Ax, By)的最后一项一定是这个元素!
  6. 为什么呢?为了方便,我们令t=Ax=By, 我们用反证法:假设L(x,y)最后一项不是t
  7. 则要么L(x,y)为空序列(别忘了这个),要么L(x,y)的最后一项是AaBb t, 且显然有a<x,b<y。无论是哪种情况我们都可以把t接到这个L(x,y)后面,从而得到一个更长的公共子序列。矛盾!
  8. 如果我们从序列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)是最长公共子序列矛盾。
  9. 因此L(x,y)=L(x-,y-)最后接上元素t,也就是说:
  10. LCS(Ax, By) = LCS(x - , y - ) +
  11.  
  12. 第()种情况:Ax By
  13. 仍然设t=L(Ax,By)的最后一个字符,或者L(Ax,By)是空序列(这时t是未定义值不等于任何值)。
  14. tAxtBy至少有一个成立,因为t不能同时等于两个不同的值嘛!
  15. 2.1 如果tAx,则有L(x,y)=L(x-,y),因为根本没Ax的事嘛。
  16. 也就是说:LCS(x,y) = LCS(x , y)
  17. 2.2 如果tBy,同理有L(x,y)= L(x,y-)。
  18. 也就是说:LCS(x,y) = LCS(x, y )
  19. 可是,我们事先并不知道t,由定义,我们取最大的一个,因此这种情况下,有LCS(x,y)=max(LCS(x–,y),LCS(x,y–))。
  20.  
  21. 看看目前我们已经得到了什么结论:
  22. LCS(x,y) =
  23. () LCS(x - ,y - ) + 如果Ax By
  24. () max(LCS(x , y) , LCS(x, y )) 如果Ax By
  25. 这是一个显然的递推式,光有递推可不行,初值是什么呢?
  26. 显然,一个空序列和任何序列的最长公共子序列都是空序列!所以我们有:
  27. LCS(x,y) =
  28. () LCS(x - ,y - ) + 如果Ax By
  29. () max(LCS(x , y) , LCS(x, y )) 如果Ax By
  30. () 如果x=0或者y=
  31.  
  32. 到此我们求出了计算最长公共子序列长度的递推公式。我们实际上计算了一个(n + )行(m + )列的表格(行是0..n,列是0..m),也就这个二维度数组LCS(n,m)。

证明过程

  1. #include <iostream>
  2. #include <cstring>
  3. using namespace std;
  4. char sz1[];
  5. char sz2[];
  6. int maxLen[][];
  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 = ;i <= length1; i ++ ) maxLen[i][] = ;
  16. for( j = ;j <= length2; j ++ ) maxLen[][j] = ;
  17. for( i = ;i <= length1;i ++ )
  18. {
  19. for( j = ; j <= length2; j ++ )
  20. {
  21. if( sz1[i-] == sz2[j-] )
  22. maxLen[i][j] = maxLen[i-][j-] + ;
  23. else
  24. maxLen[i][j] = max(maxLen[i][j-],maxLen[i-][j]);
  25. }
  26. }
  27. cout << maxLen[length1][length2] << endl;
  28. }
  29. return ;
  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+]; //共有length1+1行
  12. for(i = ; i < length1+; i++)
  13. c[i] = new int[length2+];//共有length2+1列
  14.  
  15. for(i = ; i < length1+; i++)
  16. c[i][]=; //第0列都初始化为0
  17. for(j = ; j < length2+; j++)
  18. c[][j]=; //第0行都初始化为0
  19.  
  20. for(i = ; i < length1+; i++)
  21. {
  22. for(j = ; j < length2+; j++)
  23. {
  24. if(str1[i-]==str2[j-])//由于c[][]的0行0列没有使用,c[][]的第i行元素对应str1的第i-1个元素
  25. {
  26. c[i][j]=c[i-][j-]+;
  27. b[i][j]=; //输出公共子串时的搜索方向
  28. }
  29. else if(c[i-][j]>c[i][j-])
  30. {
  31. c[i][j]=c[i-][j];
  32. b[i][j]=;
  33. }
  34. else
  35. {
  36. c[i][j]=c[i][j-];
  37. b[i][j]=-;
  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 = ; i < length1+; 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== || j==)
  58. return ;
  59. if(b[i][j]==)
  60. {
  61. PrintLCS(b, str1, i-, j-);//从后面开始递归,所以要先递归到子串的前面,然后从前往后开始输出子串
  62. printf("%c",str1[i-]);//c[][]的第i行元素对应str1的第i-1个元素
  63. }
  64. else if(b[i][j]==)
  65. PrintLCS(b, str1, i-, j);
  66. else
  67. PrintLCS(b, str1, i, j-);
  68. }
  69.  
  70. int main(void)
  71. {
  72. char str1[],str2[];
  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+];
  82. for(i= ; i < length1+; i++)
  83. b[i] = new int[length2+];
  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 = ; i < length1+; i++)//释放动态申请的二维数组
  90. delete[] b[i];
  91. delete[] b;
  92. system("pause");
  93. return ;
  94. }

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

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

空间上的优化:

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

  1. for( i = ;i <= length1;i ++ )
  2. {
  3. for( j = ; j <= length2; j ++ )
  4. {
  5. if( sz1[i-] == sz2[j-] ) maxLen[i][j] = maxLen[i-][j-] + ;
  6. else maxLen[i][j] = max(maxLen[i][j-],maxLen[i-][j]);
  7. }
  8. }

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

代码如下:(参考来源

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

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

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

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

参考上图,我们建立一个二维数组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[][maxN];
  9. char ans[maxN][maxN]={};
  10.  
  11. void printLCS(int len1,int len2);//输出一个最长公共子序列
  12. int main()
  13. {
  14. int i,j,length1,length2,cur=;
  15. freopen("poj1458.in","r",stdin);
  16. while( cin >> sz1 >> sz2 )
  17. {
  18. memset(ans,,sizeof(char)*maxN*maxN);
  19. length1 = strlen( sz1);
  20. length2 = strlen( sz2);
  21. for( i=;i<; i++ ) maxLen[i][]=;
  22. for( j=;j<=length2;j++ ) maxLen[][j]=;
  23. cur=;
  24.  
  25. for( i = ;i <= length1;i ++ )
  26. {
  27. cur ^= ;
  28. for( j = ; j <= length2; j ++ )
  29. {
  30. if( sz1[i-] == sz2[j-] )
  31. {
  32. maxLen[cur][j] = maxLen[cur^][j-] + ;
  33. ans[i][j]=;
  34. }
  35. else
  36. {
  37. //maxLen[cur][j] = max(maxLen[cur][j-1],maxLen[cur^1][j]);
  38. if(maxLen[cur][j-]>maxLen[cur^][j])
  39. {
  40. maxLen[cur][j]=maxLen[cur][j-];
  41. ans[i][j]=;
  42. }
  43. else
  44. {
  45. maxLen[cur][j]=maxLen[cur^][j];
  46. ans[i][j]=;
  47. }
  48. }
  49. }
  50. }
  51. cout << maxLen[cur][length2] << endl;
  52. if(maxLen[cur][length2]>) printLCS(length1,length2);
  53. }
  54. return ;
  55. }
  56. void printLCS(int len1,int len2)//输出一个最长公共子序列
  57. {
  58. char temp[maxN];
  59. int i=len1,j=len2,k=;
  60. while(ans[i][j]!=)
  61. {
  62. if(ans[i][j]==) { temp[k++]=sz1[i-]; i--;j--; }
  63. else if(ans[i][j]==)
  64. {
  65. j--;
  66. }
  67. else if(ans[i][j]==)
  68. {
  69. i--;
  70. }
  71. }
  72. for(k--;k>=;k--) printf("%c",temp[k]);
  73. printf("\n");
  74. }

最长公共子序列(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. HipHop PHP & HHVM资料收集

    百度百科 HipHop PHP实战(详解web运行模式) 百度 PHP7和HHVM的性能之争

  2. 如何在windows2003(IIS6)下配置IIS,使其支持cshtml

    在开发环境机器上,安装WEB PAGES 后,会在 C:\Program Files (x86)\Microsoft ASP.NET\ASP.NET Web Pages 的下产生DLL 其中 Micr ...

  3. react-router的browserHistory/react-router-dom的BrowserRouter刷新页面404问题解决

    前端解决: '/' 表示把所有的url都发给代理https://api.example.com bypass 表示不需要发给发给代理服务器的条件 如下配置,可以监听https://api.exampl ...

  4. 「BZOJ」「3262」陌上花开

    CDQ分治 WA :在solve时,对y.z排序以后,没有处理「y.z相同」的情况,也就是说可能(1,2,3)这个点被放到了(2,2,3)的后面,也就是统计答案在前,插入该点在后……也就没有统计到! ...

  5. [转]教你修复win7中复制粘贴失效的问题

    教你修复win7中复制粘贴失效的问题 发布时间:2018-01-17             使用win7系统的时候,我们经常需要对立面的内容进行复制粘贴来引用一些网站的内容,不过最近有网友在使用这个 ...

  6. Shell编程之数组使用

    记录一下shell中数组的使用 主要是数组元素的创建,元素的增.删.改操作. #!/bin/bash #基本数组操作 a=( ) ##()表示空数组 ]} echo "所有元素: " ...

  7. Maven自定义Archetype(zz)

    原文地址:http://www.cnblogs.com/javalouvre/p/5858162.html Maven提供了archetype帮助我们快速构建项目骨架,很便捷.但是,中央仓库中的arc ...

  8. MySql 5.7安装(随机密码,修改默认密码)两个坑

    MySql 5.7安装(随机密码,修改默认密 下载了MySql 最新版本,安装的过程中,发现了很多新特性 1.data目录不见了 在进行my-default.ini配置的时候 (需要配置 # base ...

  9. 学 Win32 汇编[28] - 跳转指令: JMP、JECXZ、JA、JB、JG、JL、JE、JZ、JS、JC、JO、JP 等

    http://www.cnblogs.com/del/archive/2010/04/16/1713886.html 跳转指令分三类:一.无条件跳转: JMP;二.根据 CX.ECX 寄存器的值跳转: ...

  10. ios 内存管理总结

    在ios 中 项目有两个内存管理方式 第一种,arc 方式,编译器编译时,自动给obj 加上 release  实现要求 1. 设置项目 将 Objective-C Automatic Referen ...