力扣题目链接
给定两个单词 word1
和 word2
,返回使得 word1
和 word2
相同所需的最小步数。
每步 可以删除任意一个字符串中的一个字符。
示例 1:
输入: word1 = "sea", word2 = "eat"
输出: 2
解释: 第一步将 "sea" 变为 "ea" ,第二步将 "eat "变为 "ea"
示例 2:
输入:word1 = "leetcode", word2 = "etco"
输出:4
本题和动态规划:115.不同的子序列基本相同,只要求出两个字符串的最长公共子序列长度即可,那么除了最长公共子序列之外的字符都是必须删除的,最后用两个字符串的总长度减去两个最长公共子序列的长度就是删除的最少步数。
public int minDistance(String word1, String word2) {
// 初始化
int[][] dp = new int[word1.length() + 1][word2.length() + 1];
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 - 1][j], dp[i][j - 1]);
}
}
}
return word1.length() + word2.length() - dp[word1.length()][word2.length()] * 2;
}
本题和动态规划:115.不同的子序列相比,这次是两个字符串可以相互删了
dp[i][j]
:以i-1
为结尾的字符串word1,和以j-1
位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数。
此时说明当前的两个字符相同就不需要删除,就看两个字符串的再往前一个数字是否一样,即dp[i][j] = dp[i - 1][j - 1]
word1[i - 1]
,此时先对word1做一次删除操作,然后比较的就是word1[i - 2]
和word2[j - 1]
,即最少操作次数为dp[i][j] = dp[i - 1][j] + 1
word2[j - 1]
,此时先对word2做一次删除操作,然后比较的就是word1[i - 1]
和word2[j - 2]
,即最少操作次数为dp[i][j] = dp[i][j - 1] + 1
word1[i - 1]
和word2[j - 1]
,操作的最少次数为dp[i - 1][j - 1] + 2
那最后当然是取最小值,所以当word1[i - 1] 与 word2[j - 1]不相同的时候,递推公式:dp[i][j] = min({dp[i - 1][j - 1] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1})
因为dp[i - 1][j - 1] + 1
等于 dp[i - 1][j]
或 dp[i][j - 1]
,这里说明情况三包含在情况一和情况二里面(即可能先删**word1[i-1]
,再比较word1[i-2]
和word2[j-1]
后删除了word2[j-1]
;或者是先删word2[j-1]
,再比较word2[j-2]
和word1[i-1]
后删除了word1[i-1]
**)
所以递推公式可简化为:dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1)
从递推公式中,可以看出来,dp[i][0]
和 dp[0][j]
是一定要初始化的。
dp[i][0]:word2为空字符串,以i-1
为结尾的字符串word1要删除多少个元素,才能和word2相同呢,很明显要删除i
个字符,即dp[i][0] = i
。
dp[0][j]
的话同理,所以代码如下:
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;
从递推公式 dp[i][j] = min(dp[i - 1][j - 1] + 2, min(dp[i - 1][j], dp[i][j - 1]) + 1);
和dp[i][j] = dp[i - 1][j - 1]
可以看出dp[i][j]
都是根据左上方、正上方、正左方推出来的。
所以遍历的时候一定是从上到下,从左到右,这样保证dp[i][j]
可以根据之前计算出来的数值进行计算。
以示例一:word1:“sea”,word2:"eat"为例,推导dp数组状态图如下:
完整代码:
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, dp[i][j - 1] + 1);
}
}
}
return dp[word1.length()][word2.length()];
}
力扣题目链接
给你两个单词 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')
本体涉及到删除替换插入三个操作,就要分情况思考什么时候删除,什么时候替换以及什么时候插入呢?
同样的和上一题相同,定义dp数组为
dp[i][j]
表示以下标i-1
为结尾的字符串word1,和以下标j-1
为结尾的字符串word2,最近编辑距离为dp[i][j]
。
如果word1[i-1] == word2[j-1]
,此时不需要任何操作,所以dp[i][j] = dp[i-1][j-1]
;
如果word1[i-1] != word2[j-1]
,此时可以分为三种情况:
操作一:删除。word1删除一个元素,那么就是以下标i - 2为结尾的word1 与 j-1为结尾的word2的最近编辑距离,再加上一个操作,即 dp[i][j] = dp[i - 1][j] + 1;
操作二:替换。替换word1[i - 1]
,使其与word2[j - 1]
相同,此时不用增加元素,替换完后word1[i - 1]
与word2[j - 1]
就一样的了。那么此时的编辑距离就是以下标i-2
为结尾的word1 与 j-2
为结尾的word2的最近编辑距离,加上一个替换元素的操作,即 dp[i][j] = dp[i - 1][j - 1] + 1;
操作二:插入。在word1[i - 1]
后面插入一个与word2[j - 1]
相同的字符,此时要比较word2[i - 1]
的前一个字符word2[i - 2]
与当前的word1[i - 1]
的关系(看他们的编辑距离),加上一个插入元素的操作,即 dp[i][j] = dp[i][j - 1] + 1;
三种情况取最小值,即:dp[i][j] = min({dp[i - 1][j], dp[i - 1][j - 1], dp[i][j - 1]}) + 1;
再回顾一下dp[i][j]
的定义:dp[i][j]
表示以下标i-1
为结尾的字符串word1,和以下标j-1
为结尾的字符串word2,最近编辑距离为dp[i][j]
。
dp[i][0] = i
:word1(0~i-1)
要把其中的元素全部删除才能和此时为空集的word2
相同,所以要删除i
次。
dp[0][j] = j
:word2(0~j-1)
要把其中的元素全部删除才能和此时为空集的word1
相同,所以要删除j
次。
for (int i = 1; i <= m; i++) {
dp[i][0] = i;
}
for (int j = 1; j <= n; j++) {
dp[0][j] = j;
}
由四个递推公式可以知道:
dp[i][j] = dp[i - 1][j - 1]
dp[i][j] = dp[i][j - 1] + 1
dp[i][j] = dp[i - 1][j - 1] + 1
dp[i][j] = dp[i - 1][j] + 1
可以看出dp[i][j]是依赖左方,上方和左上方元素的
所以一定是从左到右,从上到下去遍历。
以示例1为例,输入:word1 = “horse”, word2 = "ros"为例,dp矩阵状态图如下:
完整代码:
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(Math.min(dp[i - 1][j] + 1, dp[i - 1][j - 1] + 1), dp[i][j - 1] + 1);
}
}
}
return dp[word1.length()][word2.length()];
}