动态规划算法解LCS问题

作者 July 二零一零年十二月三十一日

本文参考:微软面试100题系列V0.1版第19、56题、算法导论、维基百科。

第一部分、什么是动态规划算法

ok,咱们先来了解下什么是动态规划算法。

动态规划一般也只能应用于有最优子结构的问题。最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似)。简单地说,问题能够分解成子问题来解决。

动态规划算法分以下4个步骤:

  1. 描述最优解的结构
  2. 递归定义最优解的值
  3. 按自底向上的方式计算最优解的值 //此3步构成动态规划解的基础。
  4. 由计算出的结果构造一个最优解。 //此步如果只要求计算最优解的值时,可省略。

好,接下来,咱们讨论适合采用动态规划方法的最优化问题的俩个要素:最优子结构性质,和子问题重叠性质。

  • 最优子结构

如果问题的最优解所包含的子问题的解也是最优的,我们就称该问题具有最优子结构性质(即满足最优化原理)。意思就是,总问题包含很多个子问题,而这些子问题的解也是最优的。

  • 重叠子问题

子问题重叠性质是指在用递归算法自顶向下对问题进行求解时,每次产生的子问题并不总是新问题,有些子问题会被重复计算多次。动态规划算法正是利用了这种子问题的重叠性质,对每一个子问题只计算一次,然后将其计算结果保存在一个表格中,当再次需要计算已经计算过的子问题时,只是在表格中简单地查看一下结果,从而获得较高的效率。

第二部分、动态规划算法解LCS问题

下面,咱们运用此动态规划算法解此LCS问题。有一点必须声明的是,LCS问题即最长公共子序列问题,它要求所求得的字符在所给的字符串中是连续的(例如:输入两个字符串BDCABA和ABCBDAB,字符串BCBA和BDAB都是是它们的最长公共子序列,则输出它们的长度4,并打印任意一个子序列)。

ok,咱们马上进入面试题第56题的求解,即运用经典的动态规划算法:

2.0、LCS问题描述

56.最长公共子序列。
题目:如果字符串一的所有字符按其在字符串中的顺序出现在另外一个字符串二中,
则字符串一称之为字符串二的子串。

注意,并不要求子串(字符串一)的字符必须连续出现在字符串二中。
请编写一个函数,输入两个字符串,求它们的最长公共子串,并打印出最长公共子串。
例如:输入两个字符串BDCABA和ABCBDAB,字符串BCBA和BDAB都是是它们的最长公共子序列,则输出它们的长度4,并打印任意一个子序列。

分析:求最长公共子序列(Longest Common Subsequence, LCS)是一道非常经典的动态规划题,因此一些重视算法的公司像MicroStrategy都把它当作面试题。

事实上,最长公共子序列问题也有最优子结构性质。

记:

Xi=﹤x1,⋯,xi﹥即X序列的前i个字符 (1≤i≤m)(前缀)

Yj=﹤y1,⋯,yj﹥即Y序列的前j个字符 (1≤j≤n)(前缀)

假定Z=﹤z1,⋯,zk﹥∈LCS(X , Y)。

  • xm=yn(最后一个字符相同),则不难用反证法证明:该字符必是X与Y的任一最长公共子序列Z(设长度为k)的最后一个字符,即有zk = xm = yn 且显然有Zk-1∈LCS(Xm-1 , Yn-1)即Z的前缀Zk-1是Xm-1与Yn-1的最长公共子序列。此时,问题化归成求Xm-1与Yn-1的LCS(LCS(X , Y)的长度等于LCS(Xm-1 , Yn-1)的长度加1)。

  • xm≠yn,则亦不难用反证法证明:要么Z∈LCS(Xm-1, Y),要么Z∈LCS(X , Yn-1)。由于zk≠xm与zk≠yn其中至少有一个必成立,若zk≠xm则有Z∈LCS(Xm-1 , Y),类似的,若zk≠yn 则有Z∈LCS(X , Yn-1)。此时,问题化归成求Xm-1与Y的LCS及X与Yn-1的LCS。LCS(X , Y)的长度为:max{LCS(Xm-1 , Y)的长度, LCS(X , Yn-1)的长度}。

由于上述当xm≠yn的情况中,求LCS(Xm-1 , Y)的长度与LCS(X , Yn-1)的长度,这两个问题不是相互独立的:两者都需要求LCS(Xm-1,Yn-1)的长度。另外两个序列的LCS中包含了两个序列的前缀的LCS,故问题具有最优子结构性质考虑用动态规划法。

也就是说,解决这个LCS问题,你要求三个方面的东西:1、LCS(Xm-1,Yn-1)+1;2、LCS(Xm-1,Y),LCS(X,Yn-1);3、max{LCS(Xm-1,Y),LCS(X,Yn-1)}

2.1、最长公共子序列的结构

最长公共子序列的结构有如下表示:

设序列X=<x1, x2, …, xm>和Y=<y1, y2, …, yn>的一个最长公共子序列Z=<z1, z2, …, zk>,则:

  1. 若xm=yn,则zk=xm=yn且Zk-1是Xm-1和Yn-1的最长公共子序列;
  2. 若xm≠yn且zk≠xm ,则Z是Xm-1和Y的最长公共子序列;
  3. 若xm≠yn且zk≠yn ,则Z是X和Yn-1的最长公共子序列。

其中Xm-1=<x1, x2, …, xm-1>,Yn-1=<y1, y2, …, yn-1>,Zk-1=<z1, z2, …, zk-1>。

2.2、子问题的递归结构

由最长公共子序列问题的最优子结构性质可知,要找出X=<x1, x2, …, xm>和Y=<y1, y2, …, yn>的最长公共子序列,可按以下方式递归地进行:当xm=yn时,找出Xm-1和Yn-1的最长公共子序列,然后在其尾部加上xm(=yn)即可得X和Y的一个最长公共子序列。当xm≠yn时,必须解两个子问题,即找出Xm-1和Y的一个最长公共子序列及X和Yn-1的一个最长公共子序列。这两个公共子序列中较长者即为X和Y的一个最长公共子序列。

由此递归结构容易看到最长公共子序列问题具有子问题重叠性质。例如,在计算X和Y的最长公共子序列时,可能要计算出X和Yn-1及Xm-1和Y的最长公共子序列。而这两个子问题都包含一个公共子问题,即计算Xm-1和Yn-1的最长公共子序列。

与矩阵连乘积最优计算次序问题类似,我们来建立子问题的最优值的递归关系。用c[i,j]记录序列Xi和Yj的最长公共子序列的长度。其中Xi=<x1, x2, …, xi>,Yj=<y1, y2, …, yj>。当i=0或j=0时,空序列是Xi和Yj的最长公共子序列,故c[i,j]=0。其他情况下,由定理可建立递归关系如下:

2.3、计算最优值

直接利用上节节末的递归式,我们将很容易就能写出一个计算c[i,j]的递归算法,但其计算时间是随输入长度指数增长的。由于在所考虑的子问题空间中,总共只有θ(m*n)个不同的子问题,因此,用动态规划算法自底向上地计算最优值能提高算法的效率。

计算最长公共子序列长度的动态规划算法LCS_LENGTH(X,Y)以序列X=<x1, x2, …, xm>和Y=<y1, y2, …, yn>作为输入。输出两个数组c[0..m ,0..n]和b[1..m ,1..n]。其中c[i,j]存储Xi与Yj的最长公共子序列的长度,b[i,j]记录指示c[i,j]的值是由哪一个子问题的解达到的,这在构造最长公共子序列时要用到。最后,X和Y的最长公共子序列的长度记录于c[m,n]中。

[cpp] view plaincopyprint?
  1. Procedure LCS_LENGTH(X,Y);
  2. begin
  3. m:=length[X];
  4. n:=length[Y];
  5. for i:=1 to m do c[i,0]:=0;
  6. for j:=1 to n do c[0,j]:=0;
  7. for i:=1 to m do
  8. for j:=1 to n do
  9. if x[i]=y[j] then
  10. begin
  11. c[i,j]:=c[i-1,j-1]+1;
  12. b[i,j]:="↖";
  13. end
  14. else if c[i-1,j]≥c[i,j-1] then
  15. begin
  16. c[i,j]:=c[i-1,j];
  17. b[i,j]:="↑";
  18. end
  19. else
  20. begin
  21. c[i,j]:=c[i,j-1];
  22. b[i,j]:="←"
  23. end;
  24. return(c,b);
  25. end;

由算法LCS_LENGTH计算得到的数组b可用于快速构造序列X=<x1, x2, …, xm>和Y=<y1, y2, …, yn>的最长公共子序列。首先从b[m,n]开始,沿着其中的箭头所指的方向在数组b中搜索。

  • 当b[i,j]中遇到"↖"时(意味着xi=yi是LCS的一个元素),表示Xi与Yj的最长公共子序列是由Xi-1与Yj-1的最长公共子序列在尾部加上xi得到的子序列;
  • 当b[i,j]中遇到"↑"时,表示Xi与Yj的最长公共子序列和Xi-1与Yj的最长公共子序列相同;
  • 当b[i,j]中遇到"←"时,表示Xi与Yj的最长公共子序列和Xi与Yj-1的最长公共子序列相同。

这种方法是按照反序来找LCS的每一个元素的。由于每个数组单元的计算耗费Ο(1)时间,算法LCS_LENGTH耗时Ο(mn)。

2.4、构造最长公共子序列

下面的算法LCS(b,X,i,j)实现根据b的内容打印出Xi与Yj的最长公共子序列。通过算法的调用LCS(b,X,length[X],length[Y]),便可打印出序列X和Y的最长公共子序列。

[cpp] view plaincopyprint?
  1. Procedure LCS(b,X,i,j);
  2. begin
  3. if i=0 or j=0 then return;
  4. if b[i,j]="↖" then
  5. begin
  6. LCS(b,X,i-1,j-1);
  7. print(x[i]); {打印x[i]}
  8. end
  9. else if b[i,j]="↑" then LCS(b,X,i-1,j)
  10. else LCS(b,X,i,j-1);
  11. end;

在算法LCS中,每一次的递归调用使i或j减1,因此算法的计算时间为O(m+n)。

例如,设所给的两个序列为X=和Y=。由算法LCS_LENGTH和LCS计算出的结果如下图所示:

我来说明下此图(参考算法导论)。在序列X={A,B,C,B,D,A,B}和 Y={B,D,C,A,B,A}上,由LCS_LENGTH计算出的表c和b。第i行和第j列中的方块包含了c[i,j]的值以及指向b[i,j]的箭头。在c[7,6]的项4,表的右下角为X和Y的一个LCS的长度。对于i,j>0,项c[i,j]仅依赖于是否有xi=yi,及项c[i-1,j]和c[i,j-1]的值,这几个项都在c[i,j]之前计算。为了重构一个LCS的元素,从右下角开始跟踪b[i,j]的箭头即可,这条路径标示为阴影,这条路径上的每一个“↖”对应于一个使xi=yi为一个LCS的成员的项(高亮标示)。

所以根据上述图所示的结果,程序将最终输出:“B C B A”,或“B D A B”。

可能还是有读者对上面的图看的不是很清楚,下面,我再通过对最大子序列,最长公共子串与最长公共子序列的比较来阐述相关问题@Orisun:

  • 最大子序列:最大子序列是要找出由数组成的一维数组中和最大的连续子序列。比如{5,-3,4,2}的最大子序列就是{5,-3,4,2},它的和是8,达到最大;而{5,-6,4,2}的最大子序列是{4,2},它的和是6。你已经看出来了,找最大子序列的方法很简单,只要前i项的和还没有小于0那么子序列就一直向后扩展,否则丢弃之前的子序列开始新的子序列,同时我们要记下各个子序列的和,最后找到和最大的子序列。更多请参看:程序员编程艺术第七章、求连续子数组的最大和。
  • 最长公共子串:找两个字符串的最长公共子串,这个子串要求在原字符串中是连续的。其实这又是一个序贯决策问题,可以用动态规划来求解。我们采用一个二维矩阵来记录中间的结果。这个二维矩阵怎么构造呢?直接举个例子吧:"bab"和"caba"(当然我们现在一眼就可以看出来最长公共子串是"ba"或"ab")

       b  a  b

    c  0  0  0

    a  0  1  0

    b  1  0  1

    a  0  1  0

    我们看矩阵的斜对角线最长的那个就能找出最长公共子串。

    不过在二维矩阵上找最长的由1组成的斜对角线也是件麻烦费时的事,下面改进:当要在矩阵是填1时让它等于其左上角元素加1。

       b  a  b

    c  0  0  0

    a  0  1  0

    b  1  0  2

    a  0  2  0

    这样矩阵中的最大元素就是最长公共子串的长度。

    在构造这个二维矩阵的过程中由于得出矩阵的某一行后其上一行就没用了,所以实际上在程序中可以用一维数组来代替这个矩阵。

  • 最长公共子序列LCS问题:最长公共子序列与最长公共子串的区别在于最长公共子序列不要求在原字符串中是连续的,比如ADE和ABCDE的最长公共子序列是ADE。

    我们用动态规划的方法来思考这个问题如是求解。首先要找到状态转移方程:

    等号约定,C1是S1的最右侧字符,C2是S2的最右侧字符,S1‘是从S1中去除C1的部分,S2'是从S2中去除C2的部分。

    LCS(S1,S2)等于:

(1)LCS(S1,S2’)

(2)LCS(S1’,S2)

(3)如果C1不等于C2:LCS(S1’,S2’);如果C1等于C2:LCS(S1',S2')+C1;

边界终止条件:如果S1和S2都是空串,则结果也是空串。

下面我们同样要构建一个矩阵来存储动态规划过程中子问题的解。这个矩阵中的每个数字代表了该行和该列之前的LCS的长度。与上面刚刚分析出的状态转移议程相对应,矩阵中每个格子里的数字应该这么填,它等于以下3项的最大值:

(1)上面一个格子里的数字

(2)左边一个格子里的数字

(3)左上角那个格子里的数字(如果C1不等于C2);左上角那个格子里的数字+1(如果C1等于C2)

举个例子:

     G  C  T  A

   0  0  0  0  0

G  0  1  1  1  1

B  0  1  1  1  1

T  0  1  1  2  2

A 0  1  1  2  3

填写最后一个数字时,它应该是下面三个的最大者:

(1)上边的数字2

(2)左边的数字2

(3)左上角的数字2+1=3,因为此时C1==C2

所以最终结果是3。

在填写过程中我们还是记录下当前单元格的数字来自于哪个单元格,以方便最后我们回溯找出最长公共子串。有时候左上、左、上三者中有多个同时达到最大,那么任取其中之一,但是在整个过程中你必须遵循固定的优先标准。在我的代码中优先级别是左上>左>上。

下图给出了回溯法找出LCS的过程:

2.5、算法的改进

对于一个具体问题,按照一般的算法设计策略设计出的算法,往往在算法的时间和空间需求上还可以改进。这种改进,通常是利用具体问题的一些特殊性。

例如,在算法LCS_LENGTH和LCS中,可进一步将数组b省去。事实上,数组元素c[i,j]的值仅由c[i-1,j-1],c[i-1,j]和c[i,j-1]三个值之一确定,而数组元素b[i,j]也只是用来指示c[i,j]究竟由哪个值确定。因此,在算法LCS中,我们可以不借助于数组b而借助于数组c本身临时判断c[i,j]的值是由c[i-1,j-1],c[i-1,j]和c[i,j-1]中哪一个数值元素所确定,代价是Ο(1)时间。既然b对于算法LCS不是必要的,那么算法LCS_LENGTH便不必保存它。这一来,可节省θ(mn)的空间,而LCS_LENGTH和LCS所需要的时间分别仍然是Ο(mn)和Ο(m+n)。不过,由于数组c仍需要Ο(mn)的空间,因此这里所作的改进,只是在空间复杂性的常数因子上的改进。

另外,如果只需要计算最长公共子序列的长度,则算法的空间需求还可大大减少。事实上,在计算c[i,j]时,只用到数组c的第i行和第i-1行。因此,只要用2行的数组空间就可以计算出最长公共子序列的长度。更进一步的分析还可将空间需求减至min(m, n)。

第三部分、最长公共子序列问题代码

ok,最后给出此面试第56题的代码,参考代码如下,请君自看:

[cpp] view plaincopyprint?
  1. // LCS.cpp : 定义控制台应用程序的入口点。
  2. //
  3. //copyright@zhedahht
  4. //updated@2011.12.13 July
  5. #include "stdafx.h"
  6. #include "string.h"
  7. #include <iostream>
  8. using namespace std;
  9. // directions of LCS generation
  10. enum decreaseDir {kInit = 0, kLeft, kUp, kLeftUp};
  11. void LCS_Print(int **LCS_direction,
  12. char* pStr1, char* pStr2,
  13. size_t row, size_t col);
  14. // Get the length of two strings' LCSs, and print one of the LCSs
  15. // Input: pStr1         - the first string
  16. //        pStr2         - the second string
  17. // Output: the length of two strings' LCSs
  18. int LCS(char* pStr1, char* pStr2)
  19. {
  20. if(!pStr1 || !pStr2)
  21. return 0;
  22. size_t length1 = strlen(pStr1);
  23. size_t length2 = strlen(pStr2);
  24. if(!length1 || !length2)
  25. return 0;
  26. size_t i, j;
  27. // initiate the length matrix
  28. int **LCS_length;
  29. LCS_length = (int**)(new int[length1]);
  30. for(i = 0; i < length1; ++ i)
  31. LCS_length[i] = (int*)new int[length2];
  32. for(i = 0; i < length1; ++ i)
  33. for(j = 0; j < length2; ++ j)
  34. LCS_length[i][j] = 0;
  35. // initiate the direction matrix
  36. int **LCS_direction;
  37. LCS_direction = (int**)(new int[length1]);
  38. for( i = 0; i < length1; ++ i)
  39. LCS_direction[i] = (int*)new int[length2];
  40. for(i = 0; i < length1; ++ i)
  41. for(j = 0; j < length2; ++ j)
  42. LCS_direction[i][j] = kInit;
  43. for(i = 0; i < length1; ++ i)
  44. {
  45. for(j = 0; j < length2; ++ j)
  46. {
  47. //之前此处的代码有问题,现在订正如下:
  48. if(i == 0 || j == 0)
  49. {
  50. if(pStr1[i] == pStr2[j])
  51. {
  52. LCS_length[i][j] = 1;
  53. LCS_direction[i][j] = kLeftUp;
  54. }
  55. else
  56. {
  57. if(i > 0)
  58. {
  59. LCS_length[i][j] = LCS_length[i - 1][j];
  60. LCS_direction[i][j] = kUp;
  61. }
  62. if(j > 0)
  63. {
  64. LCS_length[i][j] = LCS_length[i][j - 1];
  65. LCS_direction[i][j] = kLeft;
  66. }
  67. }
  68. }
  69. // a char of LCS is found,
  70. // it comes from the left up entry in the direction matrix
  71. else if(pStr1[i] == pStr2[j])
  72. {
  73. LCS_length[i][j] = LCS_length[i - 1][j - 1] + 1;
  74. LCS_direction[i][j] = kLeftUp;
  75. }
  76. // it comes from the up entry in the direction matrix
  77. else if(LCS_length[i - 1][j] > LCS_length[i][j - 1])
  78. {
  79. LCS_length[i][j] = LCS_length[i - 1][j];
  80. LCS_direction[i][j] = kUp;
  81. }
  82. // it comes from the left entry in the direction matrix
  83. else
  84. {
  85. LCS_length[i][j] = LCS_length[i][j - 1];
  86. LCS_direction[i][j] = kLeft;
  87. }
  88. }
  89. }
  90. LCS_Print(LCS_direction, pStr1, pStr2, length1 - 1, length2 - 1); //调用下面的LCS_Pring 打印出所求子串。
  91. return LCS_length[length1 - 1][length2 - 1];                      //返回长度。
  92. }
  93. // Print a LCS for two strings
  94. // Input: LCS_direction - a 2d matrix which records the direction of
  95. //                        LCS generation
  96. //        pStr1         - the first string
  97. //        pStr2         - the second string
  98. //        row           - the row index in the matrix LCS_direction
  99. //        col           - the column index in the matrix LCS_direction
  100. void LCS_Print(int **LCS_direction,
  101. char* pStr1, char* pStr2,
  102. size_t row, size_t col)
  103. {
  104. if(pStr1 == NULL || pStr2 == NULL)
  105. return;
  106. size_t length1 = strlen(pStr1);
  107. size_t length2 = strlen(pStr2);
  108. if(length1 == 0 || length2 == 0 || !(row < length1 && col < length2))
  109. return;
  110. // kLeftUp implies a char in the LCS is found
  111. if(LCS_direction[row][col] == kLeftUp)
  112. {
  113. if(row > 0 && col > 0)
  114. LCS_Print(LCS_direction, pStr1, pStr2, row - 1, col - 1);
  115. // print the char
  116. printf("%c", pStr1[row]);
  117. }
  118. else if(LCS_direction[row][col] == kLeft)
  119. {
  120. // move to the left entry in the direction matrix
  121. if(col > 0)
  122. LCS_Print(LCS_direction, pStr1, pStr2, row, col - 1);
  123. }
  124. else if(LCS_direction[row][col] == kUp)
  125. {
  126. // move to the up entry in the direction matrix
  127. if(row > 0)
  128. LCS_Print(LCS_direction, pStr1, pStr2, row - 1, col);
  129. }
  130. }
  131. int _tmain(int argc, _TCHAR* argv[])
  132. {
  133. char* pStr1="abcde";
  134. char* pStr2="acde";
  135. LCS(pStr1,pStr2);
  136. printf("\n");
  137. system("pause");
  138. return 0;
  139. }</iostream>

程序运行结果如下所示:

扩展:如果题目改成求两个字符串的最长公共子字符串,应该怎么求?子字符串的定义和子串的定义类似,但要求是连续分布在其他字符串中。

比如输入两个字符串BDCABA和ABCBDAB的最长公共字符串有BD和AB,它们的长度都是2。

第四部分、LCS问题的时间复杂度

算法导论上指出,

  1. 最长公共子序列问题的一个一般的算法、时间复杂度为O(mn)。然后,Masek和Paterson给出了一个O(mn/lgn)时间内执行的算法,其中n<=m,而且此序列是从一个有限集合中而来。在输入序列中没有出现超过一次的特殊情况中,Szymansk说明这个问题可在O((n+m)lg(n+m))内解决。
  2. 一篇由Gilbert和Moore撰写的关于可变长度二元编码的早期论文中有这样的应用:在所有的概率pi都是0的情况下构造最优二叉查找树,这篇论文给出一个O(n^3)时间的算法。Hu和Tucker设计了一个算法,它在所有的概率pi都是0的情况下,使用O(n)的时间和O(n)的空间,最后,Knuth把时间降到了O(nlgn)。

关于此动态规划算法更多可参考 算法导论一书第15章 动态规划问题,至于关于此面试第56题的更多,可参考我即将整理上传的答案V04版第41-60题的答案。

补充:一网友提供的关于此最长公共子序列问题的java算法源码,我自行测试了下,正确:

import java.util.Random;

public class LCS{
public static void main(String[] args){

//设置字符串长度
int substringLength1 = 20;
int substringLength2 = 20; //具体大小可自行设置

// 随机生成字符串
String x = GetRandomStrings(substringLength1);
String y = GetRandomStrings(substringLength2);

Long startTime = System.nanoTime();
// 构造二维数组记录子问题x[i]和y[i]的LCS的长度
int[][] opt = new int[substringLength1 + 1][substringLength2 + 1];

// 动态规划计算所有子问题
for (int i = substringLength1 - 1; i >= 0; i--){
for (int j = substringLength2 - 1; j >= 0; j--){
if (x.charAt(i) == y.charAt(j))
opt[i][j] = opt[i + 1][j + 1] + 1; //参考上文我给的公式。
else
opt[i][j] = Math.max(opt[i + 1][j], opt[i][j + 1]); //参考上文我给的公式。
}
}

-------------------------------------------------------------------------------------

理解上段,参考上文我给的公式:

根据上述结论,可得到以下公式,

如果我们记字符串Xi和Yj的LCS的长度为c[i,j],我们可以递归地求c[i,j]:

-------------------------------------------------------------------------------------

System.out.println("substring1:"+x);
System.out.println("substring2:"+y);
System.out.print("LCS:");

int i = 0, j = 0;
while (i < substringLength1 && j < substringLength2){
if (x.charAt(i) == y.charAt(j)){
System.out.print(x.charAt(i));
i++;
j++;
} else if (opt[i + 1][j] >= opt[i][j + 1])
i++;
else
j++;
}
Long endTime = System.nanoTime();
System.out.println(" Totle time is " + (endTime - startTime) + " ns");
}

//取得定长随机字符串
public static String GetRandomStrings(int length){
StringBuffer buffer = new StringBuffer("abcdefghijklmnopqrstuvwxyz");
StringBuffer sb = new StringBuffer();
Random r = new Random();
int range = buffer.length();
for (int i = 0; i < length; i++){
sb.append(buffer.charAt(r.nextInt(range)));
}
return sb.toString();
}
}

eclipse运行结果为

substring1:akqrshrengxqiyxuloqk
substring2:tdzbujtlqhecaqgwfzbc
LCS:qheq Totle time is 818058 ns

OK,更多,请参考:程序员编程艺术第十一章、最长公共子序列(LCS)问题。完。

动态规划算法解最长公共子序列LCS问题相关推荐

  1. 用动态规划算法实现最长公共子序列问题的算法(java实现)

    用动态规划算法实现最长公共子序列问题的算法 public class longestCommonSubsequence {//构造追踪数组rec,记录子问题来源private static Strin ...

  2. 算法导论-----最长公共子序列LCS(动态规划)

    目录 一.概念梳理 二.最长公共子序列解决方案 方案1:蛮力搜索策略 方案2:动态规划策略 三.C代码实现 实现1 实现2(空间优化) 一.概念梳理   1. 子序列(subsequence): 一个 ...

  3. 算法之最长公共子序列(LCS)问题

    算法课上老师留的作业,最长公共子序列LCS(Longest Common Subsequence)问题,首先看到这个问题感觉有点复杂,和最长公共子串不同,公共子序列并不要求元素相邻,看起来只有穷举才能 ...

  4. 动态规划解最长公共子序列(LCS)(附详细填表过程)

    目录 相关概念 子序列形式化定义: 公共子序列定义: 最长公共子序列(以下简称LCS): 方法 蛮力法求解最长公共子序列: 动态规划求解最长公共子序列: 分析规律: 做法: 伪代码: 下面演示下c数组 ...

  5. 动态规划_求最长公共子序列LCS

    学习动态规划有段时间了,我自己的感觉是看到题解很明白,但是拿到新题就后脑冒汗了,费解!我知道这其实是理解不深的缘故,动态规划是解决一类问题的方法,而不是解决某个问题的解法.今天我试着去感觉一下怎么去思 ...

  6. 动态规划算法---求最长公共子序列

    最长公共子序列和最长公共子串区别        最长公共子串(Longest Common Substring)与最长公共子序列(Longest Common Subsequence)的区别: 子串要 ...

  7. DP方法(动态规划) 寻找最长公共子序列 LCS问题(c++)

    Q:有两个序列x,y.其中x={x1,x2-xm},y={y1,y2-yn}.寻找x与y的最长公共子序列. 注:子序列和子串有区别,字串是连续的,而子序列可间断. 呃(⊙﹏⊙),后面的暂时没时间写了, ...

  8. 计算机算法设计与分析 动态规划 实验报告,动态规划法解最长公共子序列(计算机算法设计与分析实验报告).doc...

    动态规划法解最长公共子序列(计算机算法设计与分析实验报告) 实报 告 实验名称:任课教师::姓 名:完成日期:二.主要实验内容及要求: 要求按动态规划法原理求解问题: 要求交互输入两个序列数据: 要求 ...

  9. vb treeview 展开子节点_详解最长公共子序列问题,秒杀三道动态规划题目

    学算法认准 labuladong 后台回复进群一起力扣? 读完本文,可以去力扣解决如下题目: 1143.最长公共子序列(Medium) 583. 两个字符串的删除操作(Medium) 712.两个字符 ...

最新文章

  1. c++ 从文本中逐行读取,并按空格对读取的一行进行分割
  2. html调用js进行MD5加密,js实现md5加密
  3. 2019第十届蓝桥杯比赛总结(B组c/c++)
  4. OpenKruise:解放 DaemonSet 运维之路
  5. jQuery笔记---选择器
  6. 学习ModSecrity Handbook之摘录
  7. 计算机学业水平考试答题卡,高一年级期末信息技术考试(含答题卡)
  8. 监督学习 | 决策树之网络搜索
  9. python2/3 模块gmpy2在linux下安装
  10. set在python中的用法_python中set的用法:详细源码示例
  11. 如何保障MySQL主从复制关系的稳定性?关键词(新特性、crash-safe)
  12. android 分享小程序到微信,Android 使用友盟分享微信小程序到微信
  13. html怎么让一行文字有滚动的效果,HTML标签marquee实现滚动效果
  14. JDK环境变量的两种配置方法——以JDK8和JDK10为例
  15. 计算机组装配置兼容,电脑组装时怎么选择配置主板
  16. 求齐次线性方程组的基础解系matlab,MATLAB学习笔记:齐次线性方程组的基础解系...
  17. SOLIDWORKS零件与装配体模板制作
  18. VS编译运行时出现exe文件无法打开的原因
  19. 基于matlab的光学薄膜特性分析,基于matlab的光学薄膜特性分析.doc
  20. 学习vb知识的方法总结

热门文章

  1. 【C 语言】字符串模型 ( 字符串翻转模型 | 借助 递归函数操作 逆序字符串操作 | strncat 函数 )
  2. 【C 语言】字符串 一级指针 内存模型 ( 指定大小字符数组 | 未指定大小字符数组 | 指向常量字符串的指针 | 指向堆内存的指针 )
  3. 【Android 安装包优化】资源混淆 ( AAPT2 资源编译工具 | resources.arsc 资源映射表 工作机制 )
  4. 【鸿蒙 HarmonyOS】UI 组件 ( 单选按钮 | RadioButton 与 RadioContainer 组件 )
  5. 【DBMS 数据库管理系统】数据库 体系化环境 ( 数据库体系化环境简介 | 四层体系化环境 | 数据集市 )
  6. 【数据挖掘】基于层次的聚类方法 ( 聚合层次聚类 | 划分层次聚类 | 族间距离 | 最小距离 | 最大距离 | 中心距离 | 平均距离 | 基于层次聚类步骤 | 族半径 )
  7. 查询字符串中字母出现的个数
  8. PE文件数字签名信息读取存储及格式具体解释图之上(历史代码,贴出学习)
  9. urllib,urlib2与httplib,urllib3
  10. 在编写flash游戏播放声音时的一个要注意的地方