转载:http://blog.csdn.net/v_JULY_v/article/details/6110269

本文是动态规划算法中,网上写得最好的一个之一。看完很容易理解。需要重点理解的部分,我会加以标红。

本文参考:微软面试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]中。

  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的最长公共子序列。

  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=<A,B,C,B,D,A,B>和Y=<B,D,C,A,B,A>。由算法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<B,C,B,A>的长度。对于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题的代码,参考代码如下,请君自看:

  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. }

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

比如输入两个字符串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)问题。完。

LSC问题(不连续问题)的更多相关文章

  1. 如何快速找到排好序的数组中最先不连续的数字N

    现在有一大堆自然数组成的小到大数组arr,其中会有123456910  这样就要找到6(最先不连续的数字) 举例:[12356789] 找到3 [012345678] 找到8 第一种:遍历数组判断是否 ...

  2. 一个面试题的解答-----从500(Id不连续)道试题库里随机抽取20道题!

    做一个考试系统的项目,现在从试题库里面随机抽取20道题 比如我题库有500道题(ID不连续).题目出现了,如何解决呢,随机抽取! 1,我们先把500道题的id存进一个长度为500的数组. 2,实现代码 ...

  3. Oracle函数over(),rank()over()作用及用法--分区(分组)求和& 不连续/连续排名

    (1)   函数:  over()的作用及用法:    -- 分区(分组)求和. RANK ( ) OVER ( [query_partition_clause] order_by_clause )D ...

  4. CListCtrl中删除多个不连续的行

    ==================================声明================================== 本文原创,转载在正文中显要的注明作者和出处,并保证文章的完 ...

  5. 生成大小为100的数组,从1到100,随机插入,不连续,也不重复[C#]

    生成大小为100的数组,从1到100,随机插入,不连续,也不重复. 实现思路 生成一个100位的集合listA,放1到100 创建一个空的集合listB,用来存放结果 创建一个变量c,临时存储生成的数 ...

  6. 【MPI学习6】MPI并行程序设计模式:具有不连续数据发送的MPI程序设计

    基于都志辉老师<MPI并行程序设计模式>第14章内容. 前面接触到的MPI发送的数据类型都是连续型的数据.非连续类型的数据,MPI也可以发送,但是需要预先处理,大概有两类方法: (1)用户 ...

  7. iptables使用multiport 添加多个不连续端口 不指定

    iptables使用multiport 添加多个不连续端口   碟舞飞扬 , 01:26 , Linux技术 , 评论(0) , 引用(0) , 阅读(12214) , Via 本站原创 大 | 中  ...

  8. VBA高效删除不连续多行

    最近在搞VBA,在感叹Excel功能强大的同时,对于新接触的一门编程语言也很烦恼.很多基础的语法都要靠网上搜索.现总结一些学习到的心得. VBA高效删除不连续多行 在一个拥有几万条数据的Excel中, ...

  9. wordpress文章ID不连续显示问题的完美解决

    在最新版的 wordpress 系统中,依然存在着文章ID不连续显示的问题,也就是我们还没有上传多少文章,在数据库里的ID号已经很大了,也就是说如果我们的博客使用的是固定链接,那么在前台显示的ID相差 ...

随机推荐

  1. C语言课设——电影院选票系统

    C语言课设--电影院选票系统 1.课题介绍 大家都爱看电影,现请参考一个熟悉电影票预订系统,实现C语言版的订票系统.了解订票如何实现的.系统主要有2类用户:管理员用户和顾客用户. 管理员用户 1.电影 ...

  2. 常用算法和Demo(Java实现)(持续更新)

    源码地址:https://github.com/zwxbest/Demo 1.顺序存储和单向链表,双向链表,循环链表的增删查改 https://github.com/zwxbest/Demo/tree ...

  3. 从零开始 —— Canvas(一)

    从零开始-Canvas 1.颜色.样式和阴影 属性 a.fillStyle(设置或返回用于填充绘画的颜色.渐变或模式) 语法:context.fillStyle = color(颜色值) | grad ...

  4. MySQL—ORM框架,sqlalchemy模块

    武老师博客:ORM框架介绍 import os #1.当一类函数公用同样参数时候,可以转变成类运行 - 分类 #2.面向对象: 数据和逻辑组合在一起了 #3. 一类事物共同用有的属性和行为(方法) # ...

  5. 阅读github上的项目源码

    1.基础资料 函数手册,类库函数手册2.和程序相关的专业资料 高数,linux文件系统3.相关项目的文档资料4.留备份,构造可运行的环境,找开始的地方 main(),5.分层次阅读,写注解,编程思想, ...

  6. 分享身为linux爱好者的成长及学习经历

    成长是无尽的阶梯,一步一步的攀登,回望来时的路,会心一笑:转过头,面对前方,无言而努力的继续攀登.现在来和linux爱好者说说我的成长经历,在我的大学时光里我从一个一无所知的少年转变成了一个见多识广的 ...

  7. keeplived

    keepalived高可用集群.   keepalived故障切换转移原理1vrrp协议:(vritual router redundancy protocol)虚拟路由冗余协议,2故障转移.keep ...

  8. c# Winfrom窗体事件中启用多线程 并用子线程修改窗体里面的属性

    昨天一个朋友问我一个问题,需求是 this.textBox1.Text = "睡眠前"; Thread.Sleep(1000); this.textBox1.Text = &quo ...

  9. redisi应用--布隆过滤器

    但是如果我们想知道某一个值是不是已经在 HyperLogLog 结构里面了,它就无能为力了,它只提供了 pfadd 和 pfcount 方法,没有提供 pfcontains 这种方法.

  10. JS--------文件操作基本方法:上传/下载

    /** * 上传文件 * @param {any} files 文件 * @param {any} data 数据 * @returns [true,文件路径] * @returns [false,异 ...