首页 > 代码库 > 字符串整理
字符串整理
最长公共子序列(longest common subsequence)
二维dp
状态dp[i][j]表示字符串x的前缀xi和字符串y的前缀yj能够构成的最长公共子序列的长度。
初始化:第0行和第0列的dp[i][0] 和 dp[0][j]都设为0.
递推:dp[i][j]=dp[i-1][j-1]+1 if(x[i]==y[j]) ; dp[i][j]= max(dp[i-1][j],dp[i][j-1]) if(x[i]!=y[j])
打印路径:1. 可以用符号记录每次选择的方向,然后从dp[m][n]向前根据符号递归打印路径。
2. 可以不用记录选择方向,每次根据dp[i][j] 与 d[i-1][j-1],dp[i-1][j],dp[i][j-1]的关系找出路径。
两者打印复杂度都是O(m+n)。
空间优化:因为计算dp[i][j]只依赖于前面的一行和一列,所以可以用dp[2][n]的空间就够了,循环使用。
public class StringConclude { public static int LCS(String x, String y) { int m = x.length(); int n = y.length(); int[][] dp = new int[m + 1][n + 1]; char[][] path = new char[m + 1][n + 1]; for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { if (x.charAt(i - 1) == y.charAt(j - 1)) { dp[i][j] = dp[i - 1][j - 1] + 1; path[i][j] = ‘\\‘; } else if (dp[i - 1][j] >= dp[i][j - 1]) { path[i][j] = ‘|‘; dp[i][j] = dp[i - 1][j]; } else { path[i][j] = ‘-‘; dp[i][j] = dp[i][j - 1]; } } } printLCS(path, x, m, n); System.out.println(); printLCS(dp, x, m, n); System.out.println(); return dp[m][n]; } private static void printLCS(int[][] dp, String x, int i, int j) { if (i == 0 || j == 0) return; int maxIdx = myMaxIdx(dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]); if (maxIdx == 0) { printLCS(dp, x, i - 1, j - 1); System.out.print(x.charAt(i - 1)); } else if (maxIdx == 1) { printLCS(dp, x, i - 1, j); } else { printLCS(dp, x, i, j - 1); } } private static int myMaxIdx(int a, int b, int c) { int max = a; int maxIdx = 0; if (b > max) { max = b; maxIdx = 1; } if (c > max) { max = c; maxIdx = 2; } return maxIdx; } private static void printLCS(char[][] path, String x, int i, int j) { if (i == 0 || j == 0) return; if (path[i][j] == ‘\\‘) { printLCS(path, x, i - 1, j - 1); System.out.print(x.charAt(i - 1)); } else if (path[i][j] == ‘|‘) printLCS(path, x, i - 1, j); else printLCS(path, x, i, j - 1); } public static void main(String[] args) { System.out.println(LCS("abcbdab", "bdcaba")); System.out.println(LCS("aaaa", "aaaa")); System.out.println(LCS("abab", "baba")); }}
最长公共子串(longest common substring)
二维dp
状态dp[i][j]表示字符串x以x[i]结尾的子串和字符串y以y[j]结尾的子串能构成的最长公共子串的长度。
初始化:第0行和第0列的dp[i][0] 和 dp[0][j]都设为0.
递推:dp[i][j]=dp[i-1][j-1]+1 if(x[i]==y[j]) ; dp[i][j]= 0 if(x[i]!=y[j])
打印路径:只需记录最长的情况下结尾的坐标即可,因为是连续的,所以可以根据最长长度向前找到子串。
空间优化:因为计算dp[i][j]只依赖于前面的一行和一列,所以可以用dp[2][n]的空间就够了,循环使用。
public static int LCSubstring(String x, String y) { int m = x.length(); int n = y.length(); int[][] dp = new int[m + 1][n + 1]; int res = 0; int xTailIdx = -1; for (int i = 1; i <= m; i++) { for (int j = 1; j <= n; j++) { if (x.charAt(i - 1) == y.charAt(j - 1)) { dp[i][j] = dp[i - 1][j - 1] + 1; if (dp[i][j] > res) { res = dp[i][j]; xTailIdx = i; } } else { dp[i][j] = 0; } } } // print the result System.out.println(x.substring(xTailIdx - res, xTailIdx)); return res; }
字符串整理