**leetcode 72. 编辑距离

【题目】72. 编辑距离

给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:

  1. 插入一个字符
  2. 删除一个字符
  3. 替换一个字符

示例 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')

【解题思路1】动态规划

官方题解没看懂,但是评论里大佬讲的看懂了,其实差不多也就是官方动图那个意思,但是习惯上从左到右从上到下,像下面这样。
在这里插入图片描述
编辑距离:word1和word2的编辑距离为X,意味着word1经过X步,变成了word2,咋变的不用管,反正知道就需要X步,并且这是个最少的步数。
定义 dp[i][j] 的含义为:word1 的前 i 个字符和 word2 的前 j 个字符的编辑距离。意思就是 word1 的前 i 个字符,变成 word2 的前 j 个字符,最少需要这么多步。
例如 word1 = “horse”, word2 = “ros”,那么 dp[3][2]=X 就表示 “hor” 和 “ro” 的编辑距离,即把 “hor” 变成 “ro” 最少需要 X 步。
如果下标为零则表示空串,比如:dp[0][2] 就表示空串""和“ro”的编辑距离

定理一:如果其中一个字符串是空串,那么编辑距离是另一个字符串的长度。比如空串“”和“ro”的编辑距离是2(做两次“插入”操作)。再比如"hor"和空串“”的编辑距离是3(做三次“删除”操作)。

定理二:当 i>0, j>0 时(即两个串都不空时)
dp[i][j] = min( dp[i-1][j]+1, dp[i][j-1]+1, dp[i-1][j-1]+int(word1[i]!=word2[j]) )。

举个例子,word1 = “abcde”, word2 = “fgh”,现在算这俩字符串的编辑距离,就是找从word1,最少多少步,能变成word2?那就有三种方式:

  • 知道"abcd"变成"fgh"多少步(假设 dp[i-1][j]=X 步),那么从"abcde"到"fgh"就是"abcde"->“abcd”->“fgh”。(一次删除,加X步,总共X+1步)
  • 知道"abcde"变成“fg”多少步(假设 dp[i][j-1]=Y 步),那么从"abcde"到"fgh"就是"abcde"->“fg”->“fgh”。(先Y步,再一次添加,加X步,总共Y+1步)
  • 知道"abcd"变成“fg”多少步(假设 dp[i][j]=Z 步),那么从"abcde"到"fgh"就是"abcde"->“fge”->“fgh”。(先不管最后一个字符,把前面的先变好,用了Z步,然后把最后一个字符给替换了。这里如果最后一个字符碰巧就一样(word1[i]=word2[j]),那就不用替换,省了一步)
  • 以上三种方式算出来选最少的,就是答案。
class Solution {
    public int minDistance(String word1, String word2) {
        int len1 = word1.length();
        int len2 = word2.length();
        if(len1==0 || len2==0){
            return len1+len2;
        }

        //dp[i][j]表示word1 的前 i 个字符和 word2 的前 j 个字符的编辑距离
        //在word1和word2前补空格,即边界状态初始化
        int[][] dp = new int[len1+1][len2+1];
        for(int i=0; i<=len1; i++){
            dp[i][0] = i;
        }
        for(int j=0; j<=len2; j++){
            dp[0][j] = j;
        }

        for(int i=1; i<=len1; i++){
            for(int j=1; j<=len2; j++){
                int p = word1.charAt(i-1)==word2.charAt(j-1) ? 0 : 1;
                dp[i][j] = Math.min( dp[i-1][j]+1, Math.min(dp[i][j-1]+1, dp[i-1][j-1]+p) ); 
                //min函数只能有两个参数
            }
        }
        return dp[len1][len2];
    }
}

(进阶虽然减少了额外空间,但容易理解混乱……)
进阶:先把二维数组的方法做出来,要还没做出来呢,先别往下看。
由定理二可知,dp[i][j]只和dp[i-1][j],dp[i][j-1],dp[i-1][j-1]三个量有关,即二维数组中,当前元素,左边,上边,左上角三个元素。
那我们不用这么大的二维数组存啊!我们就用一维数组,表示原来二维数组中的一行,然后我们就反复覆盖里面的值。dp[i-1][j]就是我当前左边的元素,dp[i][j-1]是没覆盖前我这里的值,dp[i][j]好像找不见了?那我们就单独用一个变量存着它,我们把它叫lu(left up)。

发布了140 篇原创文章 · 获赞 132 · 访问量 16万+

猜你喜欢

转载自blog.csdn.net/XunCiy/article/details/105343279