leetcode 第72题 编辑距离

题目

给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

插入一个字符
删除一个字符
替换一个字符

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/edit-distance

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

思路

拿到问题,首先想办法解决问题。最简单的就是暴力法。

暴力法

很明显,我们可以对两个字符串进行遍历,遍历过程中对比每个字符是否match。如果match,最简单了,直接跳过;如果不match,有三种可能性,一种是将这个字符替换成word2的字符、一种是将这个字符删除,对比剩下的字符串、一种是插入一个和word2中字符match的字符,然后对比剩下的字符串。

存在的问题

暴力法很明显耗时会很高,在进行删除、替换和插入之后,对比剩余字符串的时候,会有很多冗余计算。
对于冗余计算,一般用动态规划能更好一些。

动态规划

使用动态规划,其实就是使用“状态缓存”,参考文章–动态规划到底是怎么想到的

这时候的思路就和暴力法其实不一样了。需要归纳子问题和状态转移方程。

按照暴力法那种遍历的思路,我们能想到状态转移方程的大体思路,就是根据当前字符是否match,来决定接下来的计算。但是子问题应该是什么呢?是word1当前位置到最后和word2当前位置到最后,这两个字符串的编辑距离。

这里有两个思路能让自己的思考更加顺畅:

  1. 对于动态规划的问题,尽量让变量减少。比如字符串固定一端,那么对于word1和word2都只需要考虑当前位置
  2. 最好是从逆向转成顺向思考。比如这个问题,把当前位置到最后位置的对比,转换成从开始位置到当前位置的对比。

子问题变成了f(x,y),代表word1的前x个字符和word2的前y个字符,这两个字符串的编辑距离。
那么如果想要得到word1和word2的编辑距离,我们就可以从f(x,y)一步步推演到f(word1.length, word2.length)。推演方式就是状态转移方程
f ( x , y ) = { f ( x − 1 , y − 1 ) w o r d 1 [ x ] = w o r d 2 [ y ] 1 + M a t h . m i n { f ( x − 1 , y − 1 ) f ( x − 1 , y ) f ( x , y − 1 ) } w o r d 1 [ x ] ! = w o r d 2 [ y ] f(x,y)=\left\{ \begin{array}{rcl} f(x-1,y-1) & & {word1[x]=word2[y]}\\ 1+Math.min\left\{ \begin{aligned} f(x-1,y-1)\\ f(x-1,y)\\ f(x,y-1) \end{aligned} \right\} & & {word1[x] != word2[y]}\\ \end{array} \right. f(x,y)=f(x1,y1)1+Math.minf(x1,y1)f(x1,y)f(x,y1)word1[x]=word2[y]word1[x]!=word2[y]

代码

暴力法

class Solution {
    public int minDistance(String word1, String word2) {
        return minDistance(word1, 0, word2, 0);
    }

    public int minDistance(String word1, int index1, String word2, int index2) {
    	// 如果有一个字符串已经遍历结束,就直接用删除或者插入完成后续匹配
        if (word1.length() <= index1) {
            return word2.length() - index2;
        }
        if (word2.length() <= index2) {
            return word1.length() - index1;
        }

        if (word1.charAt(index1) == word2.charAt(index2)) {
            return minDistance(word1, index1 + 1, word2, index2 + 1);
        } else {
            return Math.min(
                    1 + minDistance(word1, index1 + 1, word2, index2 + 1 ),//替换
                    Math.min(1 + minDistance(word1, index1 + 1, word2, index2),// 删除
                            1 + minDistance(word1, index1, word2, index2 + 1)));// 插入
        }
    }
}

动态规划

class Solution {
    public int minDistance(String word1, String word2) {
        int [][]result = new int[word1.length()+1][word2.length()+1];
        for (int i = 1; i < word1.length() + 1; i++) {
            result[i][0] = i;
        }
        for (int j = 1; j < word2.length() + 1; j++) {
            result[0][j] = j;
        }

        for (int i = 1; i < word1.length() + 1; i++) {
            for (int j = 1; j < word2.length() + 1; j++) {
                if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                    result[i][j] = result[i -1][j-1];
                } else {
                    result[i][j] = 1+Math.min(Math.min(result[i-1][j-1],result[i-1][j]),result[i][j-1]);
                }
            }
        }
        return result[word1.length()][word2.length()];
    }
}

总结

  • 遇到问题先看能否解决,再进行优化
  • 有冗余计算,可做剪枝的问题,一般可以用动态规划。动态规划其实就是一个“状态缓存”思想
  • 动态规划和直观思路可能并不一样,可以做一些调整。比如固定一个变量来降低复杂度。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值