leetcode72. 编辑距离(动态规划-java)

265 篇文章 2 订阅
235 篇文章 0 订阅
文章介绍了LeetCode第72题编辑距离的解题思路和动态规划实现。通过给出的示例展示了如何将一个单词转换成另一个单词所需的最少操作数,包括替换、插入和删除字符。并提供了从暴力递归到动态规划的转化过程。
摘要由CSDN通过智能技术生成

leetcode72. 编辑距离

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

题目描述

给你两个单词 word1 和 word2, 请返回将 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’)

提示:
0 <= word1.length, word2.length <= 500
word1 和 word2 由小写英文字母组成

解题思路

如果看到这题,刚开始想不到动态规划的状态转移公式,可以先尝试用暴力递归写出暴力尝试,然后去改写成动态规划,

首先看暴力递归如何写,题目交代了。转化有三个操作,删除替换和插入。还有一个隐藏操作,就是字符相同时。要跳过。这样就是一个完整的操作,这几个操作怎么反应到代码中呢,我们用指针卡住两个单词的起始位置或者终点位置,然后一个一个字符去比较,相等,我们就跳过,不等就替换删除或者插入的操作。

//伪代码框架
if (word1.charAt(r1) == word2.charAt(r2)){
	//啥都不做,跳过,直接去下一个位置操作。
	process(r1 - 1.r2 - 2);
}else {
	//插入,插入后,word2 的指针移动,word1的保持不动
	process(r1,r2 - 1) + 1;
	//删除 ,word1 的指针移动,word2的保持不动
	process(r1 - 1,r2) + 1;
	//替换 word1 和qword2 指针都移动
	process(r1 - 1,r2 - 1) + 1;
	//为甚加1,递归后的可能要加上当前字符
}

根据上面伪代码,我们可以进行写出暴力递归的尝试了。只需要补充上base case,是不是很简单,网上很多资料直接上动态规划,看的头大也看不懂。
我们这题,从尾部开始向前去比较操作,你也可以从头往后比较,区别就是,base case .和指针移动方向步一样,你可以自己改写下。

代码演示


    /**
     * 最小编辑距离
     * @param word1
     * @param word2
     * @return
     */
    public int minDistance(String word1, String word2) {
        if (word1.length() == 0){
            return word2.length();
        }
        if (word2.length() == 0){
            return word1.length();
        }
        if (word1.equals(word2)){
            return 0;
        }
        int n = word1.length();
        int m = word2.length();
        int[][]dp = new int[n][m];
        return process(word1.toCharArray(),n -1,word2.toCharArray(),m - 1,dp);
    }

    /**
     * 暴力递归加上缓存,减少重复计算,不加缓存,提交会超时
     * @param word1
     * @param r1 word1 的指针
     * @param word2
     * @param r2 指针
     * @param dp 缓存表
     * @return
     */
    public int process(char[]word1,int r1,char[]word2,int r2,int[][]dp){
        //base case 同时来到-1 ,说明比较完了.返回0
        if (r1 == -1 && r2 == -1){
            return 0;
        }
        //base case r1 结束了,剩下就需要操作r2 的长度,r2 是下标,计算长度要加1
        if (r1 == -1){
            return r2 + 1;
        }
        //base case r2 结束了,剩下就需要操作r1 的长度,r1 是下标,计算长度要加1
        if (r2 == -1){
            return r1 + 1;
        }
        //缓存里有就直接拿
        if (dp[r1][r2] != 0){
            return dp[r1][r2];
        }
        int res = -1;
        if(word1[r1] == word2[r2]){
            //跳过
            res = process(word1,r1 - 1,word2,r2 - 1,dp);
        }else{
            //插入
            int p1 = process(word1,r1,word2,r2 - 1,dp);
            //删除
            int p2 = process(word1,r1 - 1,word2,r2,dp);
            //替换
            int p3 = process(word1,r1 - 1,word2,r2 - 1,dp);
            //要取最小的情况
            res =  Math.min(Math.min(p1,p2),p3) + 1;
        }
        //答案保存到缓存里
        dp[r1][r2] = res;
        return res;
    }

动态规划

看懂了暴力递归,改动态规划应该就很容易了,不过还是加个表格演示下。
更容易明白如何初始化dp 表。

在这里插入图片描述
我们可以把dp表中两边为0位置时,需要操作的长度就是字符的长度,可以直接填上,剩下位置就可以按暴力递归中的依赖关系去填上了。

代码演示

 public int minDistance(String word1, String word2){
        if (word1.length() == 0){
            return word2.length();
        }
        if (word2.length() == 0){
            return word1.length();
        }
        if (word1.equals(word2)){
            return 0;
        }
        int n = word1.length();
        int m = word2.length();
        int[][]dp = new int[n + 1][m + 1];
        //初始化第一列
        for (int i = 1; i <= n ;i++){
            dp[i][0]  = i;
        }
        /初始化第一行
        for (int j = 1;j <= m;j++){
            dp[0][j] = j;
        }
        //递归改成表中拿值得过程
        for (int i = 1; i <= n;i++){
            for (int j = 1; j <= m;j++){
                if(word1.charAt(i - 1) == word2.charAt(j - 1)){
                    dp[i][j] = dp[i - 1][j - 1];
                }else{
                    //插入
                   int p1 = dp[i][j - 1]; 
                   //删除
                   int p2 = dp[i - 1][j];
                   //替换\
                   int p3 = dp[i - 1][j - 1];
                   dp[i][j] = Math.min(Math.min(p1,p2),p3) + 1;
                }
            }
        }
        return dp[n][m];
    }

动态规划专题

leetcode1049. 最后一块石头的重量 II

leetcode123. 买卖股票的最佳时机 III

leetcode188. 买卖股票的最佳时机 IV

leetcode312. 戳气球

eetcode787. K 站中转内最便宜的航班

leetcode62. 不同路径

leetcode63. 不同路径 II

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值