本文共 4478 字,大约阅读时间需要 14 分钟。
子串应该比较好理解,至于什么是子序列,这里给出一个例子:有两个母串
比如序列bo, bg, lg在母串cnblogs与belong中都出现过并且出现顺序与母串保持一致,我们将其称为公共子序列。最长公共子序列(Longest Common Subsequence, LCS),顾名思义,是指在所有的子序列中最长的那一个。子串是要求更严格的一种子序列,要求在母串中连续地出现。在上述例子的中,最长公共子序列为blog(cnblogs, belong),最长公共子串为lo(cnblogs, belong)。
对于母串 X=<x1,x2,⋯,xm> Y=<y1,y2,⋯,yn> ,求LCS与最长公共子串。
假设 m<n , 对于母串 X ,我们可以暴力找出 2m 个子序列,然后依次在母串 Y 中匹配,算法的时间复杂度会达到指数级 O(n∗2m) 。显然,暴力求解不太适用于此类问题。
假设 Z=<z1,z2,⋯,zk> 是 X 与 Y 的LCS, 我们观察到
因此,求解LCS的问题则变成递归求解的两个子问题。但是,上述的递归求解的办法中,重复的子问题多,效率低下。改进的办法——用空间换时间,用数组保存中间状态,方便后面的计算。这就是动态规划(DP)的核心思想了。
用二维数组c[i][j]记录串 x1x2⋯xi 与 y1y2⋯yj 的LCS长度,则可得到状态转移方程
代码实现(最长公共子序列)
public static int lcs(String str1, String str2) { int len1 = str1.length(); int len2 = str2.length(); int c[][] = new int[len1+1][len2+1]; for (int i = 0; i <= len1; i++) { for( int j = 0; j <= len2; j++) { if(i == 0 || j == 0) { c[i][j] = 0; } else if (str1.charAt(i-1) == str2.charAt(j-1)) { c[i][j] = c[i-1][j-1] + 1; } else { c[i][j] = max(c[i - 1][j], c[i][j - 1]); } } } return c[len1][len2]; //返回公共子序列的最大长度} 稍作修改,即可返回最长公共子序列 i和j分别从m,n开始,递减循环直到i = 0,j = 0。其中,m和n分别为两个串的长度。 ·如果str1[i] == str2[j],则将str[i]字符插入到子序列内,i--,j--; ·如果str1[i] != str[j],则比较L[i,j-1]与L[i-1,j],L[i,j-1]大,则j--,否则i--;(如果相等,则任选一个) import java.util.Scanner;import java.util.Stack;public class longestSubstring { public static void main(String[] args) { // TODO Auto-generated method stub Scanner sc = new Scanner(System.in); while (sc.hasNext()) { String x = sc.next(); String y = sc.next(); int[][] a = lcs2(x, y); int i = 0, j = 0; for (i = 0; i <= x.length(); i++) { for (j = 0; j <= y.length(); j++) { System.out.print(a[i][j] + " "); } System.out.println(); } //输出上图中的矩阵 Stackstk = new Stack (); i = x.length(); j = y.length(); while (i > 0 && j > 0) { if (x.charAt(i - 1) == y.charAt(j - 1)) { stk.push(x.charAt(i - 1)); //从结尾开始(即矩阵的右下角)遍历序列为倒序,故引入栈来保存 i--; j--; } else if (a[i - 1][j] >= a[i][j - 1]) i--; else j--; } while (!stk.isEmpty()) { System.out.print(stk.pop()); // 输出最长公共子序列 } /** 求最长公共子序列长度 */ // int a=lcs2(x,y); // System.out.println(a); } } public static int[][] lcs2(String str1, String str2) { // 最长公共子序列 int len1 = str1.length(); int len2 = str2.length(); int c[][] = new int[len1 + 1][len2 + 1]; for (int i = 0; i <= len1; i++) { for (int j = 0; j <= len2; j++) { if (i == 0 || j == 0) { c[i][j] = 0; } else if (str1.charAt(i - 1) == str2.charAt(j - 1)) { c[i][j] = c[i - 1][j - 1] + 1; } else { c[i][j] = Math.max(c[i - 1][j], c[i][j - 1]); } } } // return c[len1][len2]; // 返回公共子序列长度 return c; // 返回公共子序列数组 }}
前面提到了子串是一种特殊的子序列,因此同样可以用DP来解决。定义数组的存储含义对于后面推导转移方程显得尤为重要,糟糕的数组定义会导致异常繁杂的转移方程。考虑到子串的连续性,将二维数组c[i][j]用来记录具有这样特点的子串——结尾为母串 x1x2⋯xi 与 y1y2⋯yj 的结尾——的长度。
得到转移方程:
最长公共子串的长度为 max(c[i,j]), i∈{ 1,⋯,m},j∈{ 1,⋯,n}
代码实现(最长公共子串)
public static int lcs(String str1, String str2) { int len1 = str1.length(); int len2 = str2.length(); int result = 0; //记录最长公共子串长度 int c[][] = new int[len1+1][len2+1]; for (int i = 0; i <= len1; i++) { for( int j = 0; j <= len2; j++) { if(i == 0 || j == 0) { c[i][j] = 0; } else if (str1.charAt(i-1) == str2.charAt(j-1)) { c[i][j] = c[i-1][j-1] + 1; result = max(c[i][j], result); } else { c[i][j] = 0; } } } return result;//返回公共子串长度}
稍微修改一下,即可返回最长公共子串。endPosition记录公共子串的结尾字符位置。subStrlength记录公共子串的长度。
public static String lcs(String str1, String str2) { int len1 = str1.length(); int len2 = str2.length(); int subStrlength=0; int endPosition=0; int c[][] = new int[len1+1][len2+1]; for (int i = 0; i <= len1; i++) { for( int j = 0; j <= len2; j++) { if(i == 0 || j == 0) { c[i][j] = 0; } else if (str1.charAt(i-1) == str2.charAt(j-1)) { c[i][j] = c[i-1][j-1] + 1; if(subStrlength
参考: 最长公共子序列与最长公共子串 http://www.cnblogs.com/en-heng/p/3963803.html 动态规划算法解LCS问题 http://blog.csdn.net/v_JULY_v/article/details/6110269 最长公共子序列(LCS)问题 http://blog.chinaunix.net/uid-26548237-id-3374211.html