算法學習--字符串--最長公共子序列(LCS)

定義html

一個序列S任意刪除若干個字符獲得新序列T,則T稱爲S的子序列算法

兩個序列X和Y的公共子序列中,長度最長的那個,定義爲X和Y的最長公共子序列(LCS)數組

如12455和3455677的最長公共子序列就是455app

注意區別最大公共子串(須要連續)ide


算法
優化

(1)暴力窮舉法(不可取)ui

(2)動態規劃spa

假設兩個序列X和Y,設Xi爲X序列的前i個字符,Yi爲Y序列的前i個字符htm

LCS(X, Y)爲X和Y的最長公共子序列遞歸

若Xm=Yn(最後一個字符相同),則LCS(X, Y)的最後一個字符Zk=Xm=Yn

    即LCS(Xm, Yn) = LCS(Xm-1, Yn-1) + Xm

若Xm!=Yn(最後一個字符不相同),則LCS(Xm, Yn) =LCS(Xm-1, Yn)或LCS(Xm, Yn) =LCS(Xm, Yn-1)

    即LCS(Xm, Yn) =max{LCS(Xm-1, Yn), LCS(Xm, Yn-1)}


相關算法題

Delete Operation for Two Strings

Given two words word1 and word2, find the minimum number of steps required to make word1 and word2 the same, where in each step you can delete one character in either string.

Example 1:

Input: "sea", "eat"Output: 2Explanation: You need one step to make "sea" to "ea" and another step to make "eat" to "ea".

Note:

  1. The length of given words won't exceed 500.

  2. Characters in given words can only be lower-case letters.


該題實則能夠轉換成LCS的問題,假設s1,s2兩個序列長度是m,n,則本題的結果能夠經過m+n-2*LCS(s1,s2)得到

接下來就來解決如何求LCS(s1,s2),也就是上文提到的LCS算法的實現

Approach #1 Using Longest Common Subsequence [Time Limit Exceeded](用遞歸實現)

public class Solution {
    public int minDistance(String s1, String s2) {
        return s1.length() + s2.length() - 2 * lcs(s1, s2, s1.length(), s2.length());
    }
    public int lcs(String s1, String s2, int m, int n) {
        if (m == 0 || n == 0)
            return 0;
        if (s1.charAt(m - 1) == s2.charAt(n - 1))
            return 1 + lcs(s1, s2, m - 1, n - 1);
        else
            return Math.max(lcs(s1, s2, m, n - 1), lcs(s1, s2, m - 1, n));
    }
}

這種方法的時間複雜度爲O(2max(m,n)),空間複雜度O(max(m,n)),實際上有些結果有重複計算,能夠改進該算法

Approach #2 Longest Common Subsequence with Memoization [Accepted](空間換時間,依舊使用遞歸)

public class Solution {
    public int minDistance(String s1, String s2) {
        int[][] memo = new int[s1.length() + 1][s2.length() + 1];
        return s1.length() + s2.length() - 2 * lcs(s1, s2, s1.length(), s2.length(), memo);
    }
    public int lcs(String s1, String s2, int m, int n, int[][] memo) {
        if (m == 0 || n == 0)
            return 0;
        if (memo[m][n] > 0)
            return memo[m][n];
        if (s1.charAt(m - 1) == s2.charAt(n - 1))
            memo[m][n] = 1 + lcs(s1, s2, m - 1, n - 1, memo);
        else
            memo[m][n] = Math.max(lcs(s1, s2, m, n - 1, memo), lcs(s1, s2, m - 1, n, memo));
        return memo[m][n];
    }
}

這種方法的時間複雜度爲O(m*n),空間複雜度O(m*n),實際上就是用一個二維數組記錄已經計算過的值避免重複計算,可是仍是使用到了遞歸的思想

Approach #3 Using Longest Common Subsequence- Dynamic Programming [Accepted](空間換時間,掃描LCS(Xi, Yi)每個點經過附近的點算出來)

public class Solution {
    public int minDistance(String s1, String s2) {
        int[][] dp = new int[s1.length() + 1][s2.length() + 1];
        for (int i = 0; i <= s1.length(); i++) {
            for (int j = 0; j <= s2.length(); j++) {
                if (i == 0 || j == 0)
                    continue;
                if (s1.charAt(i - 1) == s2.charAt(j - 1))
                    dp[i][j] = 1 + dp[i - 1][j - 1];
                else
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
        return s1.length() + s2.length() - 2 * dp[s1.length()][s2.length()];
    }
}

這種方法的時間複雜度爲O(m*n),空間複雜度O(m*n)與Approach#2的區別是二維數組不是經過遞歸計算得出,而是直接掃描每行每列把全部值都計算出來。


固然爲了進一步節省空間,該算法能夠進一步優化空間複雜度,發現算法中求每行的值只與本行和上一行的值有關,因此能夠僅用兩行一維數組來搞定該算法。這樣就下降了空間複雜度

相關文章
相關標籤/搜索