常用演算法思想之動態規劃的字尾思想
思路:字尾是指要解決的子問題是原問題的後半部分,如果用字串類描述,相當於子問題永遠都是原問題的後半部分 str[i:]
str[i:] 表示從下標i開始,一直到末尾的整個字串
示例
給定兩個字串A: HIEROGLYPHOLOGY
和字串B: MICHAELANGELO
,他們最長公共的子序列為
HIEROGLYPHOLOGY <--> MICHAELANGELO
可以得到最長公共子序列長度為5。分析如下:
- 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)
2表示A字串的最後一個下標,3表示B字串的最後一個下標

橫座標表示字串A中參與計算最長公共子序列長度的最後一個字元;縱座標表示字串B中參與計算最長公共子序列長度的最後一個字元
- 先比較A和B的第一個字元,看不相等,執行不相等的邏輯,所以最大公共子序列要麼在A[1:]和B[0:],要麼在A[0:]和B[1:],要麼在A[1:]和B[1:]

x 表示剩餘需要比較的子字元開始的位置
- 以 A[1:]和B[0:] 為例,首字母仍然不一樣,此時最大公共子序列要麼在 A[2:]B[0:]、要麼在A[1:]和B[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]才能確定它的取值

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

- 以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()]; } 複製程式碼