經常使用算法思想之動態規劃的後綴思想

思路:後綴是指要解決的子問題是原問題的後半部分,若是用字符串類描述,至關於子問題永遠都是原問題的後半部分 str[i:]數組

str[i:] 表示從下標i開始,一直到末尾的整個字符串bash

示例

最長公共子序列長度

給定兩個字符串A:HIEROGLYPHOLOGY 和字符串B:MICHAELANGELO,他們最長公共的子序列爲ui

HIEROGLYPHOLOGY <--> MICHAELANGELOspa

能夠獲得最長公共子序列長度爲5。分析以下:3d

  • A、B兩個字符串,若是字符串A的第一個字符和B的第一個字符如出一轍,那這個字符必定是屬於最長子序列的一個,剩餘部分爲A[1:]和B[1:],且最終結果就是在前一個結果的基礎上加上剩餘部分最長字串長度便可
  • A、B兩個字符串,若是第一個字符不同,最長公共子序列要麼包含A中的第一個字符、要麼包含B中的第一個字符、或者是兩個都不是。即最長公共字串要麼你是 A[1:]和B[0:],要麼是A[0:]和B[1:],因此最長公共字串就是 A[1:]和B[0:],A[0:]和B[1:]之間的最大值

最終要計算的結果是 dp(A.length()-1,B.length()-1)。即字符串A和字符串B的最長公共子序列長度。
假設輸入的字符串A是 HIE B是 MCHI,目標就是要計算dp(2,3)code

2表示A字符串的最後一個下標,3表示B字符串的最後一個下標cdn

橫座標表示字符串A中參與計算最長公共子序列長度的最後一個字符;縱座標表示字符串B中參與計算最長公共子序列長度的最後一個字符blog

  1. 先比較A和B的第一個字符,看不相等,執行不相等的邏輯,因此最大公共子序列要麼在A[1:]和B[0:],要麼在A[0:]和B[1:],要麼在A[1:]和B[1:]

x 表示剩餘須要比較的子字符開始的位置ip

  1. 以 A[1:]和B[0:] 爲例,首字母仍然不同,此時最大公共子序列要麼在 A[2:]B[0:]、要麼在A[1:]和B[1:]

  • 表示當前圖表中沒有寫這個分支,只看挑選的分支執行路徑
  1. 以A[1:]和B[1:]爲例,首字母仍然不同,它的最長字串就是A[1:]B[2:]或者是A[2:]B[1:],考慮到這只是個子串,那最終在計算分別如下標1結尾的字符串A和B的最長公共字串中,須要看前面的結果A[1]B[0]和A[0]B[1]的最大值是那個,於是必須先計算出A[0]b[1]才能肯定它的取值

  1. 以A[1:]B[2:]爲例,A[1]和B[2]不相等,它須要計算的 最長子序列就是A[1:]B[3:]或者是A[2]B[2],一樣的要計算A中以1結尾的字串和B中以2結尾的字串的最大子序列長度,先要看下A[0]B[2]的值

  1. 以A[1:]B[3:]爲例,A[1]和B[3]同樣,可是須要去計算A[0]B[3]

從上面的分析過程能夠看到,要計算對應的位置的值,必須先把它以前的值都準備好,才能繼續進行,也就是說,若是以前已經計算過,就能夠利用它繼續計算,不然只能回過頭來再計算一遍,這樣也不划算,既然如此,就能夠按照從橫座標0開始,一行一行的填充數據。字符串

當A取下標0的時候,就是隻有1個字母和整個B字符串去對比,當A取下標1的時候,就是A[0:1]去和B對比,對應的操做順序以下

顯示按照藍線,而後是綠線最後是黃線,而後計算出值。

public int longestCommonSubsequence(String A, String B) {
        // 特殊狀況直接返回
        if(A==null || "".equals(A) || B==null || "".equals(B)){
            return 0;
        }
        int length=0;
        int [][] arr=new int[A.length()+1][B.length()+1];
        //從1開始是由於只要當前有一個是同樣的,後面的至少和他保持一致,最長序列不會比它少,若是從0開始,那麼須要有額外的邏輯去保證第0行的正確性,而從1開始就能夠很好的利用現有的邏輯,沒必要寫過多的冗餘代碼
        for(int i=1;i<=A.length();i++){
           for(int j=1;j<=B.length();j++){
               if(A.charAt(i-1)==B.charAt(j-1)){
                   arr[i][j]=arr[i-1][j-1]+1;
               }else{
                   arr[i][j]=Math.max(arr[i-1][j],arr[i][j-1]);
               }
               
           }
        }
        return arr[A.length()][B.length()];
        
    }
複製代碼

編輯距離

給兩個字符串 word1 和 word2,找到最少的步驟,使得word1可以變成word2,可使用的操做包括

  • 插入一個字符
  • 刪除一個字符
  • 替換一個字符

好比 "mart" 變成 "karma" 最少須要3步。分析以下
從上面的最長公共字串思想,能夠類比,要使一個字串變成另一個字串,根據提供的3中操做方式,分別要去這三種可能性的最小值。假定給的字符串是A和B,A要變成B,首先從第一個字符開始

  • A的第一個字符變成B的第一個字符,或者B的第一個字符變成A的第一個字符,達到條件 ,若是 A[0]==B[0],不須要變動dp[0,0]=dp[-1,-1],不然 dp[0,0]=dp[-1,-1]+1
  • 刪掉A的第一個字符,再觀察剩下的,dp[0,0]=dp[-1,0]
  • 刪除B的第一個字符,再觀察剩下的,dp[0,0]=dp[0,-1]

一樣用dp表示從第0個下標開始,須要計算的最小值上面三種狀況的最小值,數組自己是從0開始的,那從-1開始就表明一個字符都沒有,顯然這樣的編輯距離就是另一個有的長度,這也就使得初始值被創建,最終獲得的程序以下

public int minDistance(String word1, String word2) {
        
        // write your code here
        if(null == word1 || null == word2){
           return 0;
        }
        
        int[][] arr=new int[word1.length()+1][word2.length()+1];
        for(int i=0;i<=word1.length();i++){
            arr[i][0]=i;
        }
        for(int j=1;j<=word2.length();j++){
            arr[0][j]=j;
        }
        for(int i=1;i<=word1.length();i++){
            for(int j=1;j<=word2.length();j++){
                if(word1.charAt(i-1)==word2.charAt(j-1)){
                   arr[i][j]=Math.min(Math.min(arr[i][j-1]+1,arr[i-1][j]+1),arr[i-1][j-1]); 
                }else{
                  arr[i][j]=Math.min(Math.min(arr[i][j-1]+1,arr[i-1][j]+1),arr[i-1][j-1]+1);  
                }
            }
        }
        return arr[word1.length()][word2.length()];
    }
複製代碼
相關文章
相關標籤/搜索