[LeetCode]72. Edit Distance

dp,编辑距离

Posted by JinFei on March 22, 2020

题目描述

  Given two words word1 and word2, find the minimum number of operations required to convert word1 to word2.
    You have the following 3 operations permitted on a word:

    Insert a character
    Delete a character
    Replace a character

Example1:

  Input: word1 = "horse", word2 = "ros"
    Output: 3
    Explanation: 
    horse -> rorse (replace 'h' with 'r')
    rorse -> rose (remove 'r')
    rose -> ros (remove 'e')

Example2:

  Input: word1 = "intention", word2 = "execution"
    Output: 5
    Explanation: 
    intention -> inention (remove 't')
    inention -> enention (replace 'i' with 'e')
    enention -> exention (replace 'n' with 'x')
    exention -> exection (replace 'n' with 'c')
    exection -> execution (insert 'u')

解题思路

  • 参考这里
  • 这里需要维护一个二维的数组 dp,其大小为 mxn,m和n分别为 word1 和 word2 的长度。
  • dp[i][j] 表示从 word1 的前i个字符转换到 word2 的前j个字符所需要的步骤。
  • 先给这个二维数组 dp 的第一行第一列赋值,这个很简单,因为第一行和第一列对应的总有一个字符串是空串,于是转换步骤完全是另一个字符串的长度。
  • 跟以往的 DP 题目类似,难点还是在于找出状态转移方程,可以举个例子来看,比如 word1 是 “bbc”,word2 是 “abcd”,可以得到 dp 数组如下:
  •   Ø a b c d
      Ø 0 1 2 3 4
      b 1 1 1 2 3
      b 2 2 1 2 3
      c 3 3 2 1 2
    
  • 通过观察可以发现,当 word1[i] == word2[j] 时,dp[i][j] = dp[i - 1][j - 1],其他情况时,dp[i][j] 是其左,左上,上的三个值中的最小值加1,其实这里的左,上,和左上,分别对应的增加,删除,修改操作,那么可以得到状态转移方程为:

    dp[i][j] = dp[i - 1][j-1] if word1[i - 1] == word2[j - 1] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1 else

  • 使用dp[i][j]用来表示字符串1的0~i-1、字符串2的0~j-1的最小编辑距离;
  • 我们可以知道边界情况:dp[i][0] = i、dp[0][j]=j;
  • 同时对于两个字符串的子串,都能分为最后一个字符相等或者不等的情况:
  • 如果words[i-1] == words[j-1]:dp[i][j] = dp[i-1][j-1];也就是说当前的编辑距离和位置i和j的字符无关;
  • 如果words[i-1] != words[j-1]:则存在三种可能的操作:
    1. 向word1插入:dp[i][j] = dp[i][j-1] + 1;
    2. 从word1删除:dp[i][j] = dp[i-1][j] + 1;
    3. 替换word1元素:dp[i][j] = dp[i-1][j-1] + 1;
  • 从两个字符串的最后的位置开始考虑:

  • 如果最后两个字符(i,j)相等,最后两个字符就不要配对,所以等于minDistance(s1[0..i-1],s2[0…j-1]);
  • 如果最后两个字符不相等: 说明要编辑,具体可以分为三种情况:
    1. 如果 s1[i-1]和s2[j]可以配对,那我就删除s1[i]即可(删除);
    2. 如果 s1[i]和s2[j-1]可以配对,那我就在s1的后面加上s2[j]即可(插入);
    3. 如果 s1[i-1]和s2[j-1]可以配对,那我就把s1[i]修改成s2[j]即可;
  • 接着对于不相同的时候我们的情况比较复杂,我们有三种处理手段,分别是insert、replace和remove。
  • 我们先看insert操作。我们insert完之后,也就是word1中的元素会保持不变,而j会向前挪一个位置,也就是 f(i, j) = 1 + f(i, j - 1);
  • 接着考虑replace操作,replace会减少word1和word2中一个需要比较的元素(i和j会向前挪一个位置),也就是 f(i,j) = 1 + f(i - 1, j - 1)
  • 我们接着考虑最后一个remove操作,这个就很容易了,word1中会减少一个需要比较的元素,而我们j的位置不变,也就是f(i,j) = 1 + f(i - 1, j)。所以我们最后的结果相当三者取最小值即可。
class Solution {
public:
    int minDistance(string word1, string word2) {
        int row = word1.size();
        int col = word2.size();
        vector<vector<int>> dp(row + 1, vector<int>(col + 1, 0));
        for(int i = 0; i <= col; i++){  // 初始化
            dp[0][i] = i;
        }
        for(int i = 0; i <= row; i++){
            dp[i][0] = i;
        }
        for(int i = 1; i <= row; i++){
            for(int j = 1; j <= col; 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], dp[i - 1][j])) + 1;
                }
            }
        }
        return dp[row][col];
    }
};