题意
给你两个单词 word1
和 word2
,请你计算出将 word1
转换成 word2
所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
题解
解决两个字符串的动态规划问题,一般都是用两个指针 i
,j
分别指向两个字符串的最后,然后一步步往前走,缩小问题的规模
假设字符串分别为 str1
和 str2
,且 str1
变为 str2
, 每一步有4种选择:
如果str1[i] == str[j]
,那么不需要任何操作,i
和 j
都前移即可
否则,可以进行插入
,删除
,或者替换
,具体选择那个需要全试一遍
如果是插入,j
前移,因为 str1
插入了 j
对应的元素,j
已经完成比较,而 i
还没有比较
同理可得到删除和替换的状态转移方程
用递归的思想不做任何优化是这样实现的
int dfs(int i, int j)
{
if(i == -1) return j+1;
if(j == -1) return i+1;
if(str1[i] == str2[j])
return dfs(i-1, j-1)
else{
return min(
dfs(i, j-1)+1, //插入
dfs(i-1, j)+1, //删除
dfs(i-1, j-1)+1 //替换
}
}
}
显而易见,这会导致很多重复的操作,所以可以用备忘录来优化,备忘录完整的代码如下:
class Solution {
private:
string word1;
string word2;
public:
int minDistance(string word1, string word2) {
this->word1 = word1;
this->word2 = word2;
int len1= word1.size();
int len2= word2.size();
int i = len1 - 1;
int j = len2 - 1;
vector<vector<int>> memo(len1, vector<int>(len2, -1));
return dfs(i,j, memo);
}
int dfs(int i, int j, vector<vector<int>>& memo){
if(i == -1) return j+1;
if(j == -1) return i+1;
if(memo[i][j] != -1)
return memo[i][j];
if(word1[i] == word2[j])
memo[i][j] = dfs(i-1, j-1, memo);
else
memo[i][j] = min(dfs(i-1,j-1, memo), min(dfs(i-1,j, memo), dfs(i, j-1, memo))) + 1;
return memo[i][j];
}
};
当然,此题正规的做法还是动态规划
设 dp[i][j]
表示 str1[0..i]
和 str2[0..j]
的最小编辑距离,状态转移方程与递归方法类似,base case注意,因为数组的索引不能是-1
,所以 dp
数组会偏移一位, dp[..][0]
和 dp[0][..]
对应base case,代码如下:
class Solution {
public:
int minDistance(string word1, string word2) {
int m = word1.size();
int n = word2.size();
int dp[m+1][n+1];
for(int i = 0; i <= m; i++)
dp[i][0] = i;
for(int j = 0; j <= n; j++)
dp[0][j] = j;
for(int i = 1; i <= m; i++){
for(int j = 1;j <= n; j++){
if(word1[i-1] == word2[j-1])
dp[i][j] = dp[i-1][j-1];
else
dp[i][j] = min(dp[i-1][j]+1, min(dp[i][j-1]+1, dp[i-1][j-1]+1));
}
}
return dp[m][n];
}
};
有一个细节,既然每个 dp[i][j]
只和它附近的三个状态有关,空间复杂度是可以压缩成
O
(
m
i
n
(
M
,
N
)
)
O(min(M, N))
O(min(M,N))的(M,N是两个字符串的长度),但是压缩后代码的可解释性大大降低,没有意义
还有一个问题,这里只求出了最小的编辑距离,但是具体的操作并不清楚
通过给 dp
数组增加额外的信息即可
class Node{
int val;
int choice;
// 0代表跳过,1代表插入,2代表删除,3代表替换
}
Node dp[][];
通过记录的 choice
向前寻找即可