赞
踩
来源:力扣(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]; }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。