字符串整理
最长公共子序列(longest common subsequence)
最长公共子串(longest common substring)
最长递增子序列(longest increasing subsequence)
最长公共子序列(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; }
最长递增子序列(longest increasing subsequence)
定义d[i] 表示能构成长度为(i+1)的LIS的序列最后元素的最小值,根据定义d[i+1]>d[i],所以d[i]是有序的,所以对于新的元素,可以用二分查找更新特定元素的位置。
public class StringConclude { /** * 最长递增子序列 LIS * DP + BinarySearch * */ public static int LIS(int[] a) { if (a == null || a.length == 0) return 0; int len = 1;/* 存储子序列的最大长度 即MaxV当前的下标 */ int[] dp = new int[a.length];/* 存储长度i+1(len)的子序列最大元素的最小值 */ dp[0] = a[0]; for (int i = 1; i < a.length; i++) { if (a[i] > dp[len - 1]) { dp[len++] = a[i]; } else { int pos = bSearch(dp, len, a[i]); dp[pos] = a[i]; } } return len; } /* 返回MaxV[i]中刚刚大于x的那个元素的下标 */ private static int bSearch(int[] maxV, int len, int target) { int left = 0, right = len - 1; while (left <= right) { int mid = left + (right - left) / 2; if (maxV[mid] <= target) { left = mid + 1; } else { right = mid - 1; } } return left; } public static void main(String[] args) { System.out.println(LIS(new int[]{1, -3, 2, -1, 4, -5, 6, -7 })); } }
根据字符串生成后缀数组,然后对后缀数组排序,比较相邻的后缀寻找最长的重复子串。
import java.util.Arrays; public class Solution { /** * 最长重复子串 */ public static int LRS(String s) { if (s == null || s.isEmpty()) return 0; int n = s.length(); String[] suffix = new String[n]; for (int i = 0; i < n; i++) { suffix[i] = s.substring(i); } Arrays.sort(suffix); int maxLen = 0; int startIdx = -1; for (int i = 0; i < n - 1; i++) { int tmpLen = commonLen(suffix[i], suffix[i + 1]); if (tmpLen > maxLen) { maxLen = tmpLen; startIdx = i; } } System.out.println(s.substring(startIdx, startIdx + maxLen)); return maxLen; } private static int commonLen(String a, String b) { if (a == null || b == null) return 0; int i = 0, j = 0; int res = 0; while (i < a.length() && j < b.length()) { if (a.charAt(i) == b.charAt(j)) { res++; } else break; i++; j++; } return res; } public static void main(String[] args) { System.out.println(LRS("banana")); } }
双指针+hash法。
O(n)时间,O(1)空间。
public class Solution { /** * 最长不重复子串 */ public static int LNRS(String s) { if (s == null || s.isEmpty()) return 0; boolean[] exist = new boolean[256]; int i = 0, j = 0; int maxLen = 0; int startIdx = -1; for (; j < s.length(); j++) { if (!exist[s.charAt(j)]) { exist[s.charAt(j)] = true; continue; } // exist if (j - i > maxLen) { maxLen = j - i; startIdx = i; } while (i < j) { if (s.charAt(i) != s.charAt(j)) exist[i] = false; i++; if (s.charAt(i - 1) == s.charAt(j)) break; } } if (j - i > maxLen) { maxLen = j - i; startIdx = i; } System.out.println(s.substring(startIdx, startIdx + maxLen)); return maxLen; } public static void main(String[] args) { System.out.println(LNRS("abcabdefac")); } }
http://www.cnblogs.com/jdflyfly/p/3810674.html
http://www.cnblogs.com/jdflyfly/p/3812776.html