字符串整理

最长公共子序列(longest common subsequence)

最长公共子串(longest common substring

最长递增子序列(longest increasing subsequence)

最长重复子串

最长不重复子串

最长回文子串

两个字符串的编辑距离(edit distance)

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

原文地址:https://www.cnblogs.com/jdflyfly/p/3959179.html