72. 编辑距离
给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。 你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
示例 1:
输入:word1 = “horse”, word2 = “ros”
输出:3解释: horse -> rorse (将 ‘h’ 替换为 ‘r’) rorse -> rose (删除 ‘r’) rose -> ros (删除 ‘e’) 示例 2:
输入:word1 = “intention”, word2 = “execution”
输出:5解释: intention -> inention (删除 ‘t’) inention -> enention (将 ‘i’ 替换为 ‘e’) enention -> exention (将 ‘n’ 替换为 ‘x’) exention -> exection (将 ‘n’ 替换为 ‘c’) exection -> execution (插入 ‘u’) 提示:
0 <= word1.length, word2.length <= 500word1和word2由小写英文字母组成
解题思路
动态规划
经动态规划:编辑距离
解决两个字符串的动态规划问题,一般都是用两个指针i,j分别指向两个字符串的**最后**,然后一步步往前走,缩小问题的规模。
对于每对字符s1[i]和s2[j],可以有四种操作:
if s1[i] == s2[j]:啥都别做(skip)i, j 同时向前移动else:三选一:插入(insert)删除(delete)替换(replace)
1.暴力枚举
这个「三选一」到底该怎么选择呢?很简单,全试一遍,哪个操作最后得到的编辑距离最小,就选谁
base case:
- 当
j走完s2时,如果i还没走完s1,那么只能用删除操作把s1缩短为s2。 - 如果
i走完s1时j还没走完了s2,那就只能用插入操作把s2剩下的字符全部插入s1
2.备忘录优化
备忘录很好加,原来的代码稍加修改即可:
def minDistance(s1, s2) -> int:memo = dict() # 备忘录def dp(i, j):if (i, j) in memo:return memo[(i, j)]...if s1[i] == s2[j]:memo[(i, j)] = ...else:memo[(i, j)] = ...return memo[(i, j)]return dp(len(s1) - 1, len(s2) - 1)
3.DP table优化
dp[i][j]的含义和之前的 dp 函数类似:
- dp函数
dp(i, j)返回s1[0..i] 和 s2[0..j]的最小编辑距离 dp[i][j]存储s1[0..i-1] 和 s2[0..j-1]的最小编辑距离
dp 函数的 base case 是i,j等于 -1,而数组索引至少是 0,所以 dp 数组会偏移一位,dp[..][0]和dp[0][..]对应 base case。
既然 dp 数组和递归 dp 函数含义一样,也就可以直接套用之前的思路写代码,唯一不同的是,DP table 是自底向上求解,递归解法是自顶向下求解:
状态转移
class Solution {public:int minDistance(string word1, string word2) {int m = word1.size(),n = word2.size();//dp[i][j]存储的是word1[0..i-1]和word2[0..j-1]的最小编辑距离vector<vector<int>> dp(m+1,vector<int>(n+1));//创建二维数组//base casefor (int i = 1; i <= m;i++) dp[i][0] = i;for (int j = 1; 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] = mymin(dp[i][j-1] + 1,dp[i-1][j] + 1,dp[i-1][j-1] + 1);}}}return dp[m][n];}int mymin(int a,int b,int c) {return min(a,min(b,c));}};
