题目描述
给定两个单词 word1 和 word2,请你计算出将word1 转换成 word2所使用的最少操作数。
你可以对一个单词进行如下三种操作:
- 插入一个字符
- 删除一个字符
- 替换一个字符
思路和方法参考自:https://leetcode-cn.com/problems/edit-distance/solution/dong-tai-gui-hua-java-by-liweiwei1419/
思路
- 动态规划告诉我们可以 自底向上 去思考一个问题,思路是:先想这个问题最开始是什么情况,这个问题是两个字符串都为空字符串的时候,然后逐个地,一个字符一个字符加上去,在加字符的过程中考虑【状态转移】。
- 由于要考虑空字符,因为状态空间要多设置一行,多设置一列。
方法:动态规划
第一步:状态定义
- 定义 dp[i][j] 为 将word1的前 i 个字符转换成word2的前 j 个字符所使用的最少操作数
- 说明:由于要考虑空字符串,状态的下标 i 、 j 和 字符的下标 i 、j 有一个位置的偏差。
第二步:状态转移方程
- 状态转移方程通常是在做分类讨论,而分类讨论的过程,常常利用了这个问题的【最优子结构】
- 如果 word1[i - 1] == word2[j - 1],dp[i][j] = dp[i - 1][j - 1];
- 如果 word1[i - 1] != word2[j - 1],则将将word1的前 i 个字符转换成word2的前 j 个字符的最少操作数就等于下面三种情况的最小值:
- 替换: 考虑修改 word1[i - 1] 成为 word[j - 1],此时 dp[i][j] = dp[i - 1][j - 1] + 1;
- 删除: 考虑将 word1[i - 1] 删除,此时 dp[i][j] = dp[i - 1][j] + 1; 即 word1 前 i - 1 个字符到 word2前 j 个字符的最少操作数 + 1;
- 插入: 考虑将 word1 前 i 个字符的末尾添加一个字符 使得 word1[i] = word2[j - 1];此时考虑方案的时候,由于 word1[i] == word2[j - 1],状态转移时就不应该考虑 word2[j - 1],因此 word1 的前 i 个字符到 word2 的前 j - 1 个字符的最少操作数 + 1 就是此种方案的最少操作数。即 dp[i][j] = dp[i][j - 1] + 1;
- 在上面三种操作中取最小值 dp[i][j] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1;
注意: 必须得先判断两个字符串最后一个字符是否相等
第三步:base case
- 从一个字符串变成空字符串,非空字符串的长度就是编辑距离
- 以下代码其实就是在填表格的第 0 行、第 0 列:
for (int i = 1; i <= m; i++) {
dp[i][0] = i;
}
for (int j = 1; j <= n; j++) {
dp[j][0] = j;
}
第四步:思考输出
- 返回dp[m][n];
第五步:思考状态压缩
看一下【状态转移方程】:
- 如果末尾字符相等,就【抄】左上角单元格的值
- 如果末尾字符不相等,就从【正上方】(删除)、【左边】(插入)、【左上角】(替换)三个单元格的值中选出最小的一个 + 1。
因此,初看可以用【滚动数组】,更极端一点,用 2 * 2表格就可以完成操作。但是真正去做【状态压缩】的时候,由于初始化的原因,发现没有那么容易,在这里不做【状态压缩】。