演算法設計練習3——求字串轉化的最小操作次數
題目來自leetcode上的動態規劃類的練習題, 難度係數為hard。
題目要求計算把一個字串轉化成目標字串的最小操作次數。一開始我想到字串轉化的方法是按相等數目的字元遞增地進行轉化,這個方法不能有序地解決轉化的最小操作次數,所以找不到動態規劃的轉態轉移方程。後來參考discussion中的一個樓主的解題思路(https://leetcode.com/problems/edit-distance/discuss/25846/20ms-Detailed-Explained-C++-Solutions-(O(n)-Space)),發現正確的字串轉化方法應該是對源字串從前往後按遞增順序連線的字串進行轉化,而每一次轉化的目標是目標字串從前往後按遞增順序連線的字串。
舉個例子就很容易理解,比如要將abcde轉化成hijkl。
操作字串 | 目標字串 | |||||
-(空字串) | - | h | hi | hij | ... | hijkl |
a | - | h | hi | hij | ... | hijkl |
ab | - | h | hi | hij | ... | hijkl |
.... | ||||||
abcde | - | h | hi | hij | ... | hijkl |
轉化次序 | 1 | 2 | 3 | 4 | ... | 6 |
按照這樣的轉化順序,演算法通過兩個for迴圈即可解決,時間複雜度為O(mn)。同時,在這樣的轉化順序下,我們可以找到符合動態規劃的狀態轉移方程。
令dp[i][j]表示把word1的前i個字元轉化成word2的前j個字元的最小操作次數。
首先考慮兩種簡單的特殊情況,當對空字元進行轉化和轉化成空字元的時候:
dp[0][j] = j;
dp[i][0] = i;
然後對於通常情況,假設我們已經知道dp[i-1][j-1],同樣分成兩種進行考慮:
當word1[i-1] == word2[j-1]時, 在dp[i-1][j-1]的基礎上, 不再需要進行操作,則dp[i][j] = dp[i-1][j-1]
當word1[i-1] != word2[j-1]時,可能進行的操作有插入,刪除,替換三種,分三種情況討論:
插入:假如我們已經知道dp[i][j-1],即字串word1[0...i-1]能轉化成word2[0...j-2],那麼只需要在word1[0...i-1]後面插入word2[j-1]即可,所以dp[i][j] = dp[i][j-1] + 1;
刪除:類似地, dp[i][j] = dp[i-1][j] + 1;
替換:類似地,dp[i][j] = dp[i-1][j-1] + 1;
到此為止,已經找到了解決該問題的完整的狀態轉移方程,就可以用dp解決了。
class Solution {
public:
int minDistance(string word1, string word2) {
int n = word1.length(), m = word2.length();
vector<vector<int> > dp(n+1, vector<int> (m+1, 0));
for(int i = 1; i <= m; i++)
dp[0][i] = i;
for(int i = 1; i <= n; i++)
dp[i][0] = i;
for(int i = 1; i <=n; i++) {
for(int j = 1; j <= m; j++) {
if(word1[i-1] == word2[j-1])
dp[i][j] = dp[i-1][j-1];
else
dp[i][j] = 1 + min(dp[i][j-1], min(dp[i-1][j], dp[i-1][j-1]));
}
}
return dp[n][m];
}
};