1. 程式人生 > 其它 >動態規劃求解子序列問題 和 編輯距離

動態規劃求解子序列問題 和 編輯距離

動態規劃求解子序列問題

思路

這類題基本就三步:

  1. 確定動態陣列含義
  2. 寫出轉移方程
  3. 給出basecase(基礎解)

LeetCode 1143

  1. 確定動態陣列含義:dp[i] [j]為text1[0...i-1] 和 text2[0...j-1] 的 lcs(最長公共子序列) 長度
  2. 轉移方程:
    • 當text1(i-1)==text2(j-1)時,dp[i] [j]=dp[i-1] [j-1] (注意字串的下標從零開始,dp陣列的i和j就是指字串的i-1和j-1)
    • 當text1(i)!=text2(j)時,有三種情況:
      • text1[0...i-1] 和 text2[0...j-1] 的lcs長度等於text1[0...i-2] 和 text2[0...j-1]的lcs長度
      • text1[0...i-1] 和 text2[0...j-1] 的lcs長度等於text1[0...i-1] 和 text2[0...j-2]的lcs長度
      • text1[0...i-1] 和 text2[0...j-1] 的lcs長度等於text1[0...i-2] 和 text2[0...j-2]的lcs長度(這種情況下的lcs長度一定不大於前兩種,可以直接忽略)
      • 取三種情況的最大值即可
  3. 確定basecase
    • 當其中一個字串為零時,lcs長度=0

題解:

class Solution {
    public int longestCommonSubsequence(String text1, String text2) {
        int m=text1.length(),n=text2.length();
        if(m==0|| n==0) return 0;
        int[][] dp=new int[m+1][n+1];
        // 定義:text1[0..i-1] 和 text2[0..j-1] 的 lcs 長度為 dp[i][j]
        // base case: dp[0][..] = dp[..][0] = 0
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if(text1.charAt(i-1)==text2.charAt(j-1)) dp[i][j]=dp[i-1][j-1]+1;
                else {
                    dp[i][j]=Math.max(dp[i-1][j], dp[i][j-1]);
                }
            }
        }
        return dp[m][n];
    }
}

LeetCode 583

  1. 確定動態陣列含義:dp[i] [j]為使得word1[0...i-1] 和 word2[0...j-1] 相同所需的最小步數
  2. 轉移方程:
    • 當word1(i-1)==word2(j-1)時,dp[i] [j]=dp[i-1] [j-1] (注意字串的下標從零開始,dp陣列的i和j就是指字串的i-1和j-1)
    • 當word1(i)!=word2(j)時,有三種情況:
      • word1[0...i-1] 和 word2[0...j-1] 相同所需的最小步數等於word1[0...i-2] 和 text2[0...j-1]相同所需的最小步數+1(即刪除了word1[i-1])
      • word1[0...i-1] 和 word2[0...j-1] 相同所需的最小步數等於word1[0...i-1] 和 text2[0...j-2]相同所需的最小步數+1(即刪除了word2[j-1])
      • word1[0...i-1] 和 word2[0...j-1] 相同所需的最小步數等於word1[0...i-2] 和 text2[0...j-2]相同所需的最小步數+1(即刪除了word1[i-1]和word2[j-1])
      • 取三種情況的最小值即可
  3. 確定basecase
    • 當其中一個字串為零時,相同所需的最小步數為另一個字串的長度

題解:

class Solution {
    public int minDistance(String word1, String word2) {
        int m=word1.length(),n=word2.length();
        int[][] dp=new int[m+1][n+1];

        for (int i = 1; i <= m; i++) {
            dp[i][0] = i;
        }
        for (int j = 1; j <=n ; j++) {
            dp[0][j] = j;
        }
        for (int i = 1; i <=m ; i++) {
            for (int j = 1; j <=n ; j++) {
                if(word1.charAt(i-1)==word2.charAt(j-1)) dp[i][j]=dp[i-1][j-1];
                else {
                    dp[i][j]=Math.min(Math.min(dp[i-1][j]+1,dp[i][j-1]+1), dp[i-1][j-1]+2);
                }
            }
        }

        return dp[m][n];
    }

}

另一種思路,用上一題(1143)的結論,因為要使步數最小,也就是刪除兩個字串除了最長公共子序列之外的所有元素即可。程式碼如下:

class Solution {
    public int minDistance(String word1, String word2) {
        int m=word1.length(),n=word2.length();
        int lcs = longestCommonSubsequence(word1,word2,m,n);
        return m+n-2*lcs;
    }

    public int longestCommonSubsequence(String word1, String word2,int m,int n) {
        
        if(m==0|| n==0) return 0;
        int[][] dp=new int[m+1][n+1];
        // 定義:s1[0..i-1] 和 s2[0..j-1] 的 lcs 長度為 dp[i][j]
        // base case: dp[0][..] = dp[..][0] = 0
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if(word1.charAt(i-1)==word2.charAt(j-1)) dp[i][j]=dp[i-1][j-1]+1;
                else {
                    dp[i][j]=Math.max(dp[i-1][j], dp[i][j-1]);
                }
            }
        }
        return dp[m][n];
    }
}

LeetCode712

這題與上一題相似,直接給程式碼

class Solution {
    public int minimumDeleteSum(String s1, String s2) {
        int m=s1.length(),n=s2.length();
        int[][] dp=new int[m+1][n+1];
        for (int i = 1; i <= m; i++) {
            dp[i][0] = dp[i-1][0]+s1.charAt(i-1);
        }
        for (int j = 1; j <=n ; j++) {
            dp[0][j] = dp[0][j-1] + s2.charAt(j-1);
        }
        for (int i = 1; i <=m ; i++) {
            for (int j = 1; j <=n ; j++) {
                if(s1.charAt(i-1)==s2.charAt(j-1)) dp[i][j]=dp[i-1][j-1];
                else {
                    dp[i][j]=Math.min(Math.min(dp[i-1][j]+s1.charAt(i-1),dp[i][j-1]+s2.charAt(j-1)), dp[i-1][j-1]+s1.charAt(i-1)+s2.charAt(j-1));
                }
            }
        }

        return dp[m][n];
    }

}

動態規劃 編輯距離 LeetCode72

  1. 確定動態陣列含義:dp[i] [j]為word1[0...i-1] 和 word2[0...j-1] 的編輯距離
  2. 轉移方程:
    • 當word1(i-1)==word2(j-1)時,dp[i] [j]=dp[i-1] [j-1] (注意字串的下標從零開始,dp陣列的i和j就是指字串的i-1和j-1)
    • 當word1(i)!=word2(j)時,有三種情況:
      • word1[0...i-1] 和 word2[0...j-1] 相同所需的最小步數等於word1[0...i-2] 和 text2[0...j-1]相同所需的最小步數+1(即刪除了word1[i-1])
      • word1[0...i-1] 和 word2[0...j-1] 相同所需的最小步數等於word1[0...i-1] 和 text2[0...j-2]相同所需的最小步數+1(即刪除了word2[j-1])
      • word1[0...i-1] 和 word2[0...j-1] 相同所需的最小步數等於word1[0...i-2] 和 text2[0...j-2]相同所需的最小步數+1(即替換了word1[i-1]和word2[j-1]中的一個)
      • 取三種情況的最小值即可
      • 刪除一個字元等價於為另一個字串插入一個字元,顧都討論刪除
  3. 確定basecase
    • 當其中一個字串為零時,相同所需的最小步數為另一個字串的長度

題解

class Solution {
    public int minDistance(String word1, String word2) {
        int m=word1.length(),n=word2.length();
        int[][] dp =new int[m+1][n+1];
        //s1[0..i-1] 和 s2[0..j-1] 的編輯距離表示為dp[i][j]
        for (int i = 0; i <= m; i++) {
            dp[i][0] = i;
        }
        for (int i = 0; i <= n; i++) {
            dp[0][i]= i;
        }
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                if(word1.charAt(i-1)==word2.charAt(j-1)) dp[i][j]=dp[i-1][j-1];
                else dp[i][j]=Math.min(Math.min(dp[i-1][j-1], dp[i-1][j]), dp[i][j-1])+1;
            }
        }
        return dp[m][n];
    }
}