当前位置:   article > 正文

动态规划——最长公共子序列_最长公共子序列动态规划算法

最长公共子序列动态规划算法

先来讲解以下什么是最长公共子序列。最长公共子序列不是最长相同字符串,有点相似但不一样,来举个简单的例子,有字符串s1=bcdea,s2=abce,最长相同字符串是bc,最大公共部分是2;而最长公共子序列则是bce,最大公共部分是3。可以看出,公共子序列不需要连续相等,有相同的序列即可。

明白了概念之后,我们来看一下题目。


2-1 两个字符串的所有最长公共子序列 (转自PTA)

求两个字符串的所有最长公共子序列。

输入格式:

输入长度≤100的两个字符串。

输出格式:

输出两个字符串的所有最长公共子序列,若最长公共子序列多于1个,则将所有子序列按字典序从小到大排序后输出。

输入样例1:

  1. ABCBDAB
  2. BDCABA

输出样例1:

  1. BCAB
  2. BCBA
  3. BDAB

输入样例2:

  1. ABACDEF
  2. PGHIK

输出样例2:

NO

读完题目之后还有点懵的话,那就先听一下我的思路。本题要输出的是最长公共子序列,可能的情况可以分为三种:不存在、只有一条和有多条。如果我们逐个去枚举的话,如果遇到存在多条的情况,是很容易出错并且漏选。那我们可以先求出其最长的长度是多少,再用回溯法,一一找出。

求最大公共子序列的长度

  1. string s1, s2;
  2. int arr[101][101]={0};//动态规划表
  3. set <string> lsc_s;
  4. int lsc_max(int m, int n)//求出最大公共子序列的长度
  5. {
  6. for(int i=1;i<=m;i++)
  7. {
  8. for(int j=1;j<=n;j++)
  9. {
  10. if(s1[i-1]==s2[j-1])arr[i][j]=arr[i-1][j-1]+1;
  11. else arr[i][j]=max(arr[i-1][j],arr[i][j-1]);
  12. }
  13. }
  14. return arr[m][n];
  15. }

s1、s2是要输入的两个字符串,set是集合(后面再讲)。我们怎么来比较呢,设立一个二维表,即上面的arr,字符串s1为纵列,字符串s2为横行。

首先拿出s1中的第一个字符和s2中逐一比较,可得出b行b列的意思是s1中b和s2中ab比较,有1个公共的,故为1;b行c列是s1中b和s2中abc比较,也只有一个公共,也为1;再举一个例子,e行e列是s1中bcde和s2中abce比较,因为e和e相同,并且前面s1中bcd和s2中abc比较时已经有两个公共的了,所以要2+1=3;以此类推,可得出此表,同时我们也可以得出一条递推公式,即

if s1[i]==s2[j],arr[i][j]=arr[i-1][j-1]+1;

else arr[i][j]=max{ arr[i][j-1], arr[i-1][j] };

即可得出最长公共子序列的最大长度arr[i][j]。下一步就可根据回溯法来获取公共子序列的所有序列并打印。

打印所有最长公共子序列

  1. void lsc_print(int i, int j, string str)
  2. {
  3. while(i>0&&j>0)
  4. {
  5. if(s1[i-1]==s2[j-1])
  6. {
  7. i--;
  8. j--;
  9. str=s1[i]+str;
  10. }
  11. else
  12. {
  13. if(arr[i-1][j]>arr[i][j-1])i--;
  14. else if(arr[i-1][j]<arr[i][j-1])j--;
  15. else
  16. {
  17. lsc_print(i-1,j,str);
  18. lsc_print(i,j-1,str);
  19. return;
  20. }
  21. }
  22. }
  23. if(str.length())lsc_s.insert(str);
  24. }

从arr[i][j]开始回溯,当s1[i-1]==s2[j-1]时,说明此公共子序列中包括这个元素,即可把这个元素加入到临时变量str中(头插法),因为是回溯,满足条件的元素在前面;当s1[i-1]!=s2[j-1]时,说明arr[i][j]是由arr[i-1][j]或者arr[i][j-1]继承而来,所以可以判断这两个哪个比较大,就跑去那边,当两边一样大的时候,说明可能存在不同的最长子序列,所以可以进行递归分别求解。当回溯完成后,把str放入到set容器中储存起(为什么用set不用数组,因为题目要求按字典序从小到大输出,set底层是红黑树,自动帮我们排列好,感兴趣的同学可以去看看set用法和底层操作)。

最终ac代码如下:

  1. #include<iostream>
  2. #include<string>
  3. #include<set>
  4. using namespace std;
  5. string s1, s2;
  6. int arr[101][101]={0};//动态规划表
  7. set <string> lsc_s;
  8. int lsc_max(int m, int n)//求出最大公共子序列的长度
  9. {
  10. for(int i=1;i<=m;i++)
  11. {
  12. for(int j=1;j<=n;j++)
  13. {
  14. if(s1[i-1]==s2[j-1])arr[i][j]=arr[i-1][j-1]+1;
  15. else arr[i][j]=max(arr[i-1][j],arr[i][j-1]);
  16. }
  17. }
  18. return arr[m][n];
  19. }
  20. void lsc_print(int i, int j, string str)
  21. {
  22. while(i>0&&j>0)
  23. {
  24. if(s1[i-1]==s2[j-1])
  25. {
  26. i--;
  27. j--;
  28. str=s1[i]+str;
  29. }
  30. else
  31. {
  32. if(arr[i-1][j]>arr[i][j-1])i--;
  33. else if(arr[i-1][j]<arr[i][j-1])j--;
  34. else
  35. {
  36. lsc_print(i-1,j,str);
  37. lsc_print(i,j-1,str);
  38. return;
  39. }
  40. }
  41. }
  42. if(str.length())lsc_s.insert(str);
  43. }
  44. int main()
  45. {
  46. cin>>s1>>s2;
  47. int m=s1.length();
  48. int n=s2.length();
  49. int len=lsc_max(m,n);
  50. string str;
  51. lsc_print(m, n, str);
  52. set<string>::iterator it=lsc_s.begin();
  53. if(lsc_s.empty())
  54. {
  55. cout<<"NO"<<endl;
  56. return 0;
  57. }
  58. while(it!=lsc_s.end())
  59. {
  60. cout<<*it<<endl;
  61. it++;
  62. }
  63. return 0;
  64. }

如果本篇文章对你有所帮助的话,记得点赞哦!如果哪里写得不好的话,也可以评论,欢迎指正!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/凡人多烦事01/article/detail/169553
推荐阅读
相关标签
  

闽ICP备14008679号