LeetCode72編輯距離
思路
- dp[i][j] 表示 word1 到 i 位置 轉換成 word2 到 j 位置需要最少的步數。
- 當 word1[i] == word2[j],dp[i][j] = dp[i-1][j-1];
- 當 word1[i] != word2[j],dp[i][j] = min(dp[i-1][j-1],dp[i-1][j],dp[i][j-1])+1;
其中的dp[i-1][j-1]表示代替操作,dp[i-1][j]表示刪除操作,dp[i][j-1]表示插入操作。
以上的替換、刪除、插入操作都是對 word1 來說的。
代碼(自頂向下)暴力遞歸
package leetcode;
/**
* @author god-jiang
* @date 2020/2/19 17:08
*/
public class MinDistance {
public int minDistance(String word1, String word2) {
int length1 = word1.length();
int length2 = word2.length();
return min(word1, word2, length1, length2);
}
public int min(String word1, String word2, int i, int j) {
//base case
if (i == 0) {
return j;
} else if (j == 0) {
return i;
} else if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
return min(word1, word2, i - 1, j - 1);
} else {
int len1 = min(word1, word2, i - 1, j - 1) + 1;
int len2 = min(word1, word2, i - 1, j) + 1;
int len3 = min(word1, word2, i, j - 1) + 1;
return Math.min(Math.min(len1, len2), len3);
}
}
}
因爲有重複計算的過程,而且無後效性。即一個函數f(n)一旦確定,那麼之後就可以直接調用它的值,不用再關心f(n)的計算過程了,這個就是無後效性。
代碼(自底向上)動態規劃
package leetcode;
/**
* @author god-jiang
* @date 2020/2/19 17:08
*/
public class MinDistance {
public int minDistance(String word1, String word2) {
int length1 = word1.length();
int length2 = word2.length();
int[][] dp = new int[length1 + 1][length2 + 1];
//初始化base case
for (int i = 1; i <= length1; i++) {
dp[i][0] = dp[i - 1][0] + 1;
}
for (int j = 1; j <= length2; j++) {
dp[0][j] = dp[0][j - 1] + 1;
}
//填充二維數組dp表
for (int i = 1; i <= length1; i++) {
for (int j = 1; j <= length2; 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[length1][length2];
}
}
基本上動態規劃都是暴力遞歸改過來的。最長公共子序列、湊硬幣等都是通過這種方法寫出動態規劃的狀態轉移方程。沒有必要去背狀態轉移方程式,也不用一直想着最優子結構等名詞。最需要寫出暴力遞歸,觀察能不能改動態規劃即可。
遞歸就是“暴力的枚舉”,期間可能包括一大堆重複計算,而且一般時間複雜度都是O(2^N)。改成動態規劃就是“聰明的枚舉”,可以省掉重複的計算。
PS:最後看完還覺得暈的話,我建議是再看看我總結的動態規劃套路吧,應該對你會有所收穫的。
動態規劃套路文章的鏈接:動態規劃的高度套路