題目
題目連結:https://leetcode.com/problems/edit-distance/
給定兩個字串 word1
, word2
,找到最少的操作步數將 word1
轉換成 word2
。
有三種合法的操作:
- 插入一個字元
- 刪除一個字元
- 替換一個字元
範例說明
Example 1:
1 2 3 4 5 6
| Input: word1 = "horse", word2 = "ros" Output: 3 Explanation: horse -> rorse (replace 'h' with 'r') rorse -> rose (remove 'r') rose -> ros (remove 'e')
|
Example 2:
1 2 3 4 5 6 7 8
| Input: word1 = "intention", word2 = "execution" Output: 5 Explanation: intention -> inention (remove 't') inention -> enention (replace 'i' with 'e') enention -> exention (replace 'n' with 'x') exention -> exection (replace 'n' with 'c') exection -> execution (insert 'u')
|
想法
在 LeetCode - Regular Expression Matching 中,有提到過當看見兩字串匹配的題目時,幾乎可以確定此題為動態規劃,且 DP 狀態為 d(i, j)
代表 word1
的前 i
個字元與 word2
的前 j
個字元為止,編輯距離為多少。
DP 狀態
假設兩字串分別為 word1
以及 word2
,而且從索引 1 開始編號。
所以我們可以定義 DP 狀態為: d(i, j) = word1[1 ~ i], word2[1 ~ j] 是否匹配
。
DP 轉移
首先,如果 word1[i] = word2[j]
,則 word1[1 ~ i]
與 word2[1 ~ j]
所需的編輯距離等於 word1[1 ~ i - 1]
與 word2[1 ~ j - 1]
的編輯距離。所以:
d(i, j)=d(i−1, j−1) if word1[i]=word2[j]
再來考慮三種對 word1
的操作:
- 插入一個字元:則求
d(i, j - 1)
的編輯距離再加一。
- 刪除一個字元:則求
d(i - 1, j)
的編輯距離再加一。
- 替換一個字元,則求
d(i - 1, j - 1)
的編輯距離再加一。
所以:
d(i, j)=min(d(i−1, j),d(i, j−1),d(i−1, j−1)) if word1[i]=word2[j]
最後考慮邊界情況:
d(0, 0) = 0
,因為兩字串為空時相等,編輯距離為 0。
d(0, i) = i
,當 word1
為空,word2
不為空,則編輯距離為 word2
長度。
d(i, 0) = i
,當 word1
不為空,word2
為空,則編輯距離為 word1
長度。
假設 word1
長 N
,word2
長 M
,則總時間複雜度為 O(NM)
,DP 陣列為 N*M
大,所以總空間複雜度為 O(NM)
。
空間優化
可以發現 DP 轉移只需要左邊、左上、上面三個位置,因此只需要紀錄當前這排的 DP 狀態。
利用一變數 pre
代表左上角的狀態,利用 temp
紀錄上方狀態,再進行轉移。
總空間複雜度降為 O(M)
。
實作細節
因為真實情況中,word1
與 word2
皆為 Base 0(索引從 0 開始),所以要將轉移式中的 d(i, j)
都變為 d(i + 1, j + 1)
。
程式碼
O(N^2) Space
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
|
class Solution { public: int minDistance(string word1, string word2) { int n = (int)word1.length(); int m = (int)word2.length(); vector<vector<int>> dp(n + 1, vector<int>(m + 1));
for (int i = 0; i < n; i++) dp[i + 1][0] = i + 1; for (int i = 0; i < m; i++) dp[0][i + 1] = i + 1;
for (int i = 0; i < n; i++) { for (int j = 0; j < m; j++) { if (word1[i] == word2[j]) dp[i + 1][j + 1] = dp[i][j]; else dp[i + 1][j + 1] = min(min(dp[i + 1][j], dp[i][j + 1]), dp[i][j]) + 1; } } return dp[n][m]; } };
|
O(N) Space
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
|
class Solution { public: int minDistance(string word1, string word2) { int n = (int)word1.length(); int m = (int)word2.length(); vector<int> dp(m + 1);
for (int i = 0; i < m; i++) dp[i + 1] = i + 1;
for (int i = 0; i < n; i++) { int pre = dp[0]; dp[0] = i + 1; for (int j = 0; j < m; j++) { int temp = dp[j + 1]; if (word1[i] == word2[j]) dp[j + 1] = pre; else dp[j + 1] = min(min(pre, dp[j + 1]), dp[j]) + 1; pre = temp; } } return dp[m]; } };
|