题目与题解
参考资料:编辑距离总结篇
583. 两个字符串的删除操作
题目链接:583. 两个字符串的删除操作
代码随想录题解:583. 两个字符串的删除操作
视频讲解:动态规划之子序列,还是为了编辑距离做铺垫 | LeetCode:583.两个字符串的删除操作_哔哩哔哩_bilibili
解题思路:
这道题跟求最长公共子序很像,因为操作步骤要最少,意味着两个字符串删除剩下的字符要尽可能多,删除后剩下的序列又是一样的,其实最后剩下的必然是公共子序。所以只要求出最长公共子序的长度,再分别用两个字符串的长度减去公共长度,得到的两个字符串的长度和就是删除的字符长度,即所需答案。
class Solution {
public int minDistance(String word1, String word2) {
int[][] dp = new int[word1.length()+1][word2.length()+1];
int maxCommonLen = 0;
for (int i = 1; i <= word1.length(); i++) {
for (int j = 1; j <= word2.length(); j++) {
if (word1.charAt(i-1) == word2.charAt(j-1)) {
dp[i][j] = dp[i-1][j-1]+1;
} else {
dp[i][j] = Math.max(dp[i][j-1], dp[i-1][j]);
}
maxCommonLen = Math.max(maxCommonLen, dp[i][j]);
}
}
return word1.length() + word2.length() - 2*maxCommonLen;
}
}
看完代码随想录之后的想法
求最长公共子序有一点逆向思维。随想录给出了正向的解题方式。
递推公式主要着眼于每一步删除哪一个字符串的字符,分两种情况讨论
- 当word1[i - 1] 与 word2[j - 1]相同的时候
- 当word1[i - 1] 与 word2[j - 1]不相同的时候
当word1[i - 1] 与 word2[j - 1]相同的时候,dp[i][j] = dp[i - 1][j - 1];
当word1[i - 1] 与 word2[j - 1]不相同的时候,有三种情况:
情况一:删word1[i - 1],最少操作次数为dp[i - 1][j] + 1,表示删除word1的字符
情况二:删word2[j - 1],最少操作次数为dp[i][j - 1] + 1,表示删除word2的字符
情况三:同时删word1[i - 1]和word2[j - 1],操作的最少次数为dp[i - 1][j - 1] + 2
因为 dp[i][j - 1] + 1 = dp[i - 1][j - 1] + 2,所以递推公式可简化为:dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1); 从字面上理解 就是 当 同时删word1[i - 1]和word2[j - 1],dp[i][j-1] 本来就不考虑 word2[j - 1]了,那么在删 word1[i - 1]时,就达到两个元素都删除的效果,即 dp[i][j-1] + 1。
考虑到有删除的情况,所以对于下标为0的dp一定要初始化,即空字串相对有数值的字符串必须有操作。
dp[i][0]:word2为空字符串,以i-1为结尾的字符串word1要删除多少个元素,才能和word2相同呢,很明显dp[i][0] = i。dp[0][j]的话同理。
考虑递推来源,遍历顺序是从上到下,从左到右。
// dp数组中存储需要删除的字符个数
class Solution {
public int minDistance(String word1, String word2) {
int[][] dp = new int[word1.length() + 1][word2.length() + 1];
for (int i = 0; i < word1.length() + 1; i++) dp[i][0] = i;
for (int j = 0; j < word2.length() + 1; j++) dp[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)) {
dp[i][j] = dp[i - 1][j - 1];
}else{
dp[i][j] = Math.min(dp[i - 1][j - 1] + 2,
Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1));
}
}
}
return dp[word1.length()][word2.length()];
}
}
遇到的困难
正向的有点难想,所以直接利用最长公共子序的反向思维了。正向的也要掌握。
72. 编辑距离
题目链接:72. 编辑距离
代码随想录题解:72. 编辑距离
解题思路:
dp表示对当前以word1[i-1]为结尾的子字符串要转变成word2[j-1]为下标的子字符串,最少需要多少步。
递推公式同样分两种情况:
word1[i-1] = word2[j-1]时:不需要任何增删改操作,此时dp[i][j] = dp[i-1][j-1]
二者不相等时:如果是删除操作,跟上一题一样,dp[i][j] = dp[i-1][j] + 1
增和改就有点犯难了,看答案。
看完代码随想录之后的想法
增加元素操作:word1要增加一个元素,可以相当于word2删除一个元素的操作总数,所以可以简单写成dp[i][j] = dp[i][j-1] + 1
替换元素:word1[i-1] = word2[j-1]时,dp[i][j] = dp[i-1][j-1],那么要通过替换元素使word1[i-1] = word2[j-1],只需要在dp[i-1][j-1]的基础上增加一次替换操作即可,即dp[i][j] = dp[i-1][j-1]+1。
所以最终的dp[i][j]就是增删改这三个递推公式中的最小值。
初始化参考上一题正向思维的初始化方式,即空字符串要通过操作变成另一个字符串,或一个字符串要通过操作变成空字符串,需要初始化0行0列为非空字符串当前的长度。
顺序同样是按从上到下,从左到右操作。
class Solution {
public int minDistance(String word1, String word2) {
int[][] dp = new int[word1.length()+1][word2.length()+1];
for (int i = 0; i <= word1.length(); i++) dp[i][0] = i;
for (int j = 0; j <= word2.length(); j++) dp[0][j] = j;
for (int i = 1; i <= word1.length(); i++) {
for (int j = 1; j <= word2.length(); j++) {
if (word1.charAt(i-1) == word2.charAt(j-1)) {
dp[i][j] = dp[i-1][j-1];
} else {
dp[i][j] = Math.min(dp[i-1][j-1] + 1,
Math.min(dp[i-1][j] + 1, dp[i][j-1] + 1));
}
}
}
return dp[word1.length()][word2.length()];
}
}
遇到的困难
删和改的递推公式不大会,记住吧。
今日收获
学习了一系列两个序列相关关系的动态规划题,其本质思路为:
dp定义:考虑到有空数组或空串,为了初始化方便,dp一般比输入的数组多取一个长度,定义为以数组1[i-1]为结尾的子数组和数组2[j-1]的子数组的关系,该关系为题目要求的最终结果。
递推公式:一般分为两种情况:当前数组对应的值相同,得到一个递推,不相同时得到另一个,根据题目需求进行增减值
初始化:根据情况,全部初始化为0,1,或者对应的当前数组长度
方向:一般由左上角推到右下角,所以从上到下,从左到右遍历
后面二刷可以再熟悉一下。