72. 编辑距离 【困难题】【动态规划】
给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
插入一个字符,删除一个字符,替换一个字符
输入: word1 = "horse", word2 = "ros"
输出: 3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')
输入: word1 = "intention", word2 = "execution"
输出: 5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')
题目讲解
解法1:非递归一
【核心思想】
- 动态规划
【思路】
- 令
dp[i][j]
为:当遍历到字符串word1
的第i
个字符,遍历到字符串word2
的第j
个字符时,转换所需要的最少操作次数为dp[i][j]
- 若
word1[i]==word2[j]
,那么当前不需要进行任何操作,有dp[i][j]=dp[i-1][j-1]
- 若
word1[i]!=word2[j]
不相等,j就有三种操作:
- 如果把字符
word1[i]
替换成与word2[j]
相等,则有dp[i][j] = dp[i-1][j-1] + 1
- 如果在字符串
word1
末尾插入一个与word2[j]
相等的字符,则有dp[i][j] = dp[i][j-1] + 1
- 如果把字符
word1[i]
删除,则有dp[i][j]=dp[i-1] [j]+1
- 那么我们应该选择一种操作,使得
dp[i][j]
的值最小,即有dp[i][j] = min(dp[i-1][j-1],dp[i][j-1],dp[[i-1][j]]) + 1
【代码】
public int minDistance(String word1, String word2) {
int len1=word1.length();
int len2=word2.length();
int[][] dp=new int[len1+1][len2+1];
for(int i=0;i<=len2;i++)
dp[0][i]=i;
for(int j=0;j<=len1;j++)
dp[j][0]=j;
for(int i=1;i<=len1;i++){
for(int j=1;j<=len2;j++){
if(word1.charAt(i-1)==word2.charAt(j-1))
dp[i][j]=dp[i-1][j-1];
else
dp[i][j]=Math.min(Math.min(dp[i-1][j-1],dp[i-1][j]),dp[i][j-1])+1;
}
}
return dp[len1][len2];
}
解法2:非递归二
【核心思想】
- 将解法一的动态规划数组从二维降至一维
【代码】
public int minDistance(String word1, String word2) {
int len1=word1.length();
int len2=word2.length();
if(len1==0)
return len2;
if(len2==0)
return len1;
int[] dp=new int[len2+1];
for(int i=0;i<=len2;i++)
dp[i]=i;
for(int i=1;i<=len1;i++){
int pre=i-1;
for(int j=1;j<=len2;j++){
if(j==1)
dp[j-1]=i;
int temp=pre;
pre=dp[j];
if(word1.charAt(i-1)==word2.charAt(j-1))
dp[j]=temp;
else
dp[j]=Math.min(Math.min(temp,dp[j]),dp[j-1])+1;
}
}
return dp[len2];
}
解法3:递归一
【核心思想】
- 依旧是动态规划的思想,将其用递归的形式表示出来。有从前往后和从后往前两种方式,两者思路完全一致。
【代码】
//从后往前
public int minDistance(String word1, String word2) {
int len1=word1.length();
int len2=word2.length();
if(len1==0)
return len2;
if(len2==0)
return len1;
int ans=0;
if(word1.charAt(len1-1)==word2.charAt(len2-1))
ans=minDistance(word1.substring(0,len1-1),word2.substring(0,len2-1));
else{
int dis1=minDistance(word1.substring(0,len1-1),word2.substring(0,len2-1));
int dis2=minDistance(word1.substring(0,len1),word2.substring(0,len2-1));
int dis3=minDistance(word1.substring(0,len1-1),word2.substring(0,len2));
ans=Math.min(Math.min(dis1,dis2),dis3)+1;
}
return ans;
}
//从前往后
public int minDistance2(String word1, String word2) {
int m = word1.length();
int n = word2.length();
if(m==0)
return n;
if(n==0)
return m;
if(word1.charAt(0) == word2.charAt(0))
return minDistance(word1.substring(1), word2.substring(1));
else
return Math.min(1 + minDistance(word1, word2.substring(1)), Math.min(1 + minDistance(word1.substring(1), word2), 1 + minDistance(word1.substring(1), word2.substring(1))));
}
【备注】
- 递归的方法超时,是因为重复了太多比较。其改进方式如下。
解法4:递归二
【核心思想】
- 改进版本的递归,是用一个数组来记录已经比较过的字符串。该版本能通过oj检测。
【代码】
public int minDistance3(String word1, String word2) {
if (word1.length() == 0) return word2.length();
if (word2.length() == 0) return word1.length();
int[][] dp = new int[word1.length()+1][word2.length()+1];
return helper(word1, word2, 0, 0, dp);
}
private int helper(String s1, String s2, int i, int j, int[][] dp) {
if (s1.length() == i)
return s2.length() - j;
if (s2.length() == j)
return s1.length() - i;
int ans = 0;
if(dp[i][j] > 0) {
return dp[i][j];
}
if (s1.charAt(i) == s2.charAt(j)) {
ans = helper(s1, s2, i + 1, j + 1, dp);
} else {
int insert = helper(s1, s2, i, j + 1, dp);
int delete = helper(s1, s2, i + 1, j, dp);
int replace = helper(s1, s2, i + 1, j + 1, dp);
ans = Math.min(Math.min(insert, delete), replace) + 1;
}
dp[i][j] = ans;
return ans;
}
关注微信公众号“算法岗从零到无穷”,更多算法知识点告诉你。
来源:CSDN
作者:yuanninesuns
链接:https://blog.csdn.net/yuanninesuns/article/details/104572679