[Leedcode][JAVA][第72题][动态规划]
【問題描述】 [72. 編輯距離]
給你兩個(gè)單詞?word1 和?word2,請你計(jì)算出將?word1?轉(zhuǎn)換成?word2 所使用的最少操作數(shù)?。你可以對一個(gè)單詞進(jìn)行如下三種操作:插入一個(gè)字符 刪除一個(gè)字符 替換一個(gè)字符示例?1:輸入:word1 = "horse", word2 = "ros" 輸出:3 解釋: horse -> rorse (將 'h' 替換為 'r') rorse -> rose (刪除 'r') rose -> ros (刪除 'e')【解答思路】
1.動(dòng)態(tài)規(guī)劃
第 1 步:定義狀態(tài)
狀態(tài):dp[i][j] 表示將 word1[0, i) 轉(zhuǎn)換成為 word2[0, j) 的方案數(shù)。
思考狀態(tài)的方法:1、題目問什么就將什么定義為狀態(tài);2、「狀態(tài)轉(zhuǎn)移方程」怎么好推導(dǎo),就怎么定義狀態(tài);3、根據(jù)經(jīng)驗(yàn)和問題的特點(diǎn)(只有多做題了)。
說明:由于要考慮空字符,這里的下標(biāo) i 不包括 word[i],同理下標(biāo) j 不包括 word[j],從行數(shù)和列數(shù)多設(shè)置一行、一列也可以來理解這一點(diǎn),也就是狀態(tài)的下標(biāo) i 和 j 和字符的下標(biāo) i、j 有一個(gè)位置的偏差。
第 2 步:思考狀態(tài)轉(zhuǎn)移方程
狀態(tài)轉(zhuǎn)移方程通常是在做分類討論,而分類討論的過程,常常利用了這個(gè)問題的「最優(yōu)子結(jié)構(gòu)」。
情況 1:word1[i] == word2[j]
如果 word1[i] == word2[j] 成立,則將 word1[0, i) 轉(zhuǎn)換成為 word2[0, j) 的方案數(shù)就等于 將 word1[0, i - 1) 轉(zhuǎn)換成為 word2[0, j - 1) 的方案數(shù),即:
dp[i + 1][j + 1] = dp[i][j];
注意:這種情況,方案數(shù)最少,后面三種情況可以不用討論,在這里暫時(shí)忽略。
情況 2:word1[i] != word2[j]
如果 word1[i] != word2[j] ,則將 word1[0, i) 轉(zhuǎn)換成為 word2[0, j) 的方案數(shù)就等于下面 3 種情況的最少操作數(shù)(「最優(yōu)子結(jié)構(gòu)」):
考慮修改 word1[i] 成為 word2[j];
此時(shí) dp[i + 1][j + 1] = dp[i][j] + 1,這里的 1 代表了將 word1[i] 替換成為 word2[j] 這一步操作。
考慮將 word1[0, i] 的最后一個(gè)字符刪除;
此時(shí) word1[0, i - 1] 到 word2[0, j] 的最少操作數(shù) + 1+1,就是這種方案數(shù)的最少操作數(shù),即: dp[i + 1][j + 1] = dp[i][j + 1] + 1,這里的 1 代表了 word1[0, i] 的最后一個(gè)字符刪除這一步操作。
考慮將 word1[0, i] 的末尾添加一個(gè)字符使得 word1[i + 1] == word2[j];
此時(shí)考慮方案的時(shí)候,由于 word1[i + 1] == word2[j],狀態(tài)轉(zhuǎn)移就不應(yīng)該考慮 word2[j],因此 word1[0, i] 到 word2[0, j - 1] 的最少操作數(shù) + 1+1,就是這種方案數(shù)的最少操作數(shù),即: dp[i + 1][j + 1] = dp[i + 1][j] + 1,這里的 1 代表了將 word1[0, i] 的末尾添加一個(gè)字符使得 word1[i + 1] == word2[j]。(注意:可以考慮一下為什么得先討論 word1[i] == word2[j] 的情況。)
在這 3 種操作中取最小值。
dp[i + 1][j + 1] = min(dp[i][j], dp[i][j + 1], dp[i + 1][j]) + 1第 3 步:初始化
從一個(gè)字符串變成空字符串,非空字符串的長度就是編輯距離;
以下代碼其實(shí)就是在填表格的第 00 行、第 00 列。
第 4 步: 思考輸出
輸出:dp[len1][len2] 符合語義,即 word1[0, len) 轉(zhuǎn)換成 word2[0, len2) 的最小操作數(shù)。(這里 ) 表示開區(qū)間。)
第 5 步: 思考狀態(tài)壓縮
我們看一下「狀態(tài)轉(zhuǎn)移方程」:
- 如果末尾字符相等,就「抄」左上角單元格的值;
- 如果末尾字符不相等,就從「正上方」、「左邊」、「左上角」三個(gè)單元格的值中選出最小的 + 1。
因此,初看可以使用「滾動(dòng)數(shù)組」,更極端一點(diǎn),用 2 \times 22×2 表格就可以完成操作。但是真正去做「狀態(tài)壓縮」的時(shí)候,由于初始化的原因,發(fā)現(xiàn)沒有那么容易,在這里不做「狀態(tài)壓縮」。(事實(shí)上可以壓縮,但是只要是壓縮狀態(tài),必然給編碼造成一定困難,并且破壞代碼可讀性,根據(jù)情況做吧,個(gè)人覺得在空間緊張的情況下必須壓縮空間,其余不必。)
時(shí)間復(fù)雜度:O(N^2) 空間復(fù)雜度:O(N)
import java.util.Arrays;public class Solution {public int minDistance(String word1, String word2) {// 由于 word1.charAt(i) 操作會(huì)去檢查下標(biāo)是否越界,因此// 在 Java 里,將字符串轉(zhuǎn)換成字符數(shù)組是常見額操作char[] word1Array = word1.toCharArray();char[] word2Array = word2.toCharArray();int len1 = word1Array.length;int len2 = word2Array.length;// 多開一行一列是為了保存邊界條件,即字符長度為 0 的情況,這一點(diǎn)在字符串的動(dòng)態(tài)規(guī)劃問題中比較常見int[][] dp = new int[len1 + 1][len2 + 1];// 初始化:當(dāng) word 2 長度為 0 時(shí),將 word1 的全部刪除for (int i = 1; i <= len1; i++) {dp[i][0] = i;}// 當(dāng) word1 長度為 0 時(shí),就插入所有 word2 的字符for (int j = 1; j <= len2; j++) {dp[0][j] = j;}// 注意:填寫 dp 數(shù)組的時(shí)候,由于初始化多設(shè)置了一行一列,橫、縱坐標(biāo)有個(gè)偏移for (int i = 0; i < len1; i++) {for (int j = 0; j < len2; j++) {// 這是最佳情況if (word1Array[i] == word2Array[j]) {dp[i + 1][j + 1] = dp[i][j];continue;}// 否則在以下三種情況中選出步驟最少的,這是「動(dòng)態(tài)規(guī)劃」的「最優(yōu)子結(jié)構(gòu)」// 1、在下標(biāo) i 處插入一個(gè)字符int insert = dp[i + 1][j] + 1;// 2、替換一個(gè)字符int replace = dp[i][j] + 1;// 3、刪除一個(gè)字符int delete = dp[i][j + 1] + 1;dp[i + 1][j + 1] = Math.min(Math.min(insert, replace), delete);}}// 打印狀態(tài)表格進(jìn)行調(diào)試 // for (int i = 0; i <=len1; i++) { // System.out.println(Arrays.toString(dp[i])); // }return dp[len1][len2];}public static void main(String[] args) {String word1 = "horse";String word2 = "ros";Solution solution = new Solution();int res = solution.minDistance(word1, word2);System.out.println(res);} }作者:liweiwei1419 鏈接:https://leetcode-cn.com/problems/edit-distance/solution/dong-tai-gui-hua-java-by-liweiwei1419/【總結(jié)】
- 「自底向上」去考慮一個(gè)問題
思考的時(shí)候可以先將大區(qū)間拆分成小區(qū)間,求解的時(shí)候由小區(qū)間的解得到大區(qū)間的解。
- 重疊子問題
- 最優(yōu)子結(jié)構(gòu)
- 無后效性
第 1 步:定義狀態(tài)
第 2 步:思考狀態(tài)轉(zhuǎn)移方程
第 3 步:初始化
第 4 步: 思考輸出
第 5 步: 思考狀態(tài)壓縮
總結(jié)
以上是生活随笔為你收集整理的[Leedcode][JAVA][第72题][动态规划]的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: EXEJ4 生成的java exe文件更
- 下一篇: 对“粘连”footer布局的思考和总结