问题

给你两个单词 word1word2,请你计算出将 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’)

思路

编辑距离终于来了,这道题目如果大家没有了解动态规划的话,会感觉超级复杂
编辑距离是用动规来解决的经典题目,这道题目看上去好像很复杂,但用动规可以很巧妙的算出最少编辑距离

  • 确定dp数组以及下标的含义

    • **dp[i][j]** 表示以下标**i-1**为结尾的字符串**word1**,和以下标**j-1**为结尾的字符串**word2**,最近编辑距离为**dp[i][j]**
    • 这里在强调一下:为啥要表示下标i-1为结尾的字符串呢,为啥不表示下标i为结尾的字符串呢?用i来表示也可以!但我统一以下标i-1为结尾的字符串,在下面的递归公式中会容易理解一点
  • 确定递推公式

    • 在确定递推公式的时候,首先要考虑清楚编辑的几种操作,整理如下:

      • if (word1[i - 1] == word2[j - 1])
        • 不操作
      • if (word1[i - 1] != word2[j - 1])
      • 也就是如上四种情况
    • if (word1[i - 1] == word2[j - 1]) 那么说明不用任何编辑,dp[i][j]就应该是dp[i - 1][j - 1],即dp[i][j] = dp[i - 1][j - 1];

      • 那么为什么是dp[i][j] = dp[i - 1][j - 1]呢?
      • 那么就在回顾上面讲过的dp[i][j]的定义,word1[i - 1]word2[j - 1]相等了,那么就不用编辑了,以下标i-2为结尾的字符串word1和以下标j-2为结尾的字符串word2的最近编辑距离dp[i - 1][j - 1]就是dp[i][j]
    • 在整个动规的过程中,最为关键就是正确理解dp[i][j]的定义!

    • if (word1[i - 1] != word2[j - 1]),此时就需要编辑了,如何编辑呢?

      • 操作一word1增加一个元素,使其word1[i - 1]word2[j - 1]相同,那么就是以下标i-2为结尾的word1j-1为结尾的word2的最近编辑距离加上一个增加元素的操作

        • dp[i][j] = dp[i - 1][j] + 1;
      • 操作二word2添加一个元素,使其word1[i - 1]word2[j - 1]相同,那么就是以下标i-1为结尾的word1j-2为结尾的word2的最近编辑距离加上一个增加元素的操作

        • dp[i][j] = dp[i][j - 1] + 1;
      • 这里有同学发现了,怎么都是添加元素,删除元素去哪了

        • word2添加一个元素,相当于word1删除一个元素,例如 word1 = “ad” ,word2 = “a”,word2添加一个元素d,也就是相当于word1删除一个元素d,操作数是一样!
      • 操作三:替换元素,word1替换word1[i - 1],使其与word2[j - 1]相同,此时不用增加元素,那么以下标i-2为结尾的word1j-2为结尾的word2的最近编辑距离加上一个替换元素的操作

        • dp[i][j] = dp[i - 1][j - 1] + 1;
      • 综上,当 if (word1[i - 1] != word2[j - 1]) 时取最小的,即:dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;

        1. if (word1[i - 1] == word2[j - 1]) {
        2. dp[i][j] = dp[i - 1][j - 1];
        3. }
        4. else {
        5. dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;
        6. }
  • dp数组如何初始化

    • **dp[i][j]** 表示以下标**i-1**为结尾的字符串**word1**,和以下标**j-1**为结尾的字符串**word2**,最近编辑距离为**dp[i][j]**

    • 那么dp[i][0]dp[0][j] 表示什么呢?

      • dp[i][0] :以下标i-1为结尾的字符串word1,和空字符串word2,最近编辑距离为dp[i][0]
      • 那么dp[i][0]就应该是i,对空字符串做添加元素的操作就可以了,即:dp[i][0] = i;
      • 同理dp[0][j] = j;
        1. for (int i = 0; i <= word1.length(); i++) dp[i][0] = i;
        2. for (int j = 0; j <= word2.length(); j++) dp[0][j] = j;
  • 确定遍历顺序

从如下四个递推公式:

  • dp[i][j] = dp[i - 1][j - 1]
  • dp[i][j] = dp[i - 1][j - 1] + 1
  • dp[i][j] = dp[i][j - 1] + 1
  • dp[i][j] = dp[i - 1][j] + 1

可以看出dp[i][j]是依赖左方,上方和左上方元素的,如图:
640 (2).webp
所以在dp矩阵中一定是从左到右从上到下去遍历

  1. for (int i = 1; i <= word1.length(); i++) {
  2. for (int j = 1; j <= word2.length(); j++) {
  3. if (word1[i - 1] == word2[j - 1]) {
  4. dp[i][j] = dp[i - 1][j - 1];
  5. }
  6. else {
  7. dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;
  8. }
  9. }
  10. }
  • 举例推导dp数组
    • 以示例1,输入:word1 = “horse”, word2 = “ros”为例,dp矩阵状态图如下:

640 (3).webp

  1. class Solution {
  2. public int minDistance(String word1, String word2) {
  3. int[][] dp = new int[word1.length() + 1][word2.length() + 1];
  4. for (int i = 0; i <= word1.length(); i++) dp[i][0] = i;
  5. for (int j = 0; j <= word2.length(); j++) dp[0][j] = j;
  6. for (int i = 1; i <= word1.length(); i++) {
  7. for (int j = 1; j <= word2.length(); j++) {
  8. if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
  9. dp[i][j] = dp[i - 1][j - 1];
  10. }
  11. else {
  12. dp[i][j] = Math.min(Math.min(dp[i - 1][j - 1], dp[i][j - 1]),
  13. dp[i - 1][j]) + 1;
  14. }
  15. }
  16. }
  17. return dp[word1.length()][word2.length()];
  18. }
  19. }