583. 两个字符串的删除操作 - 力扣(LeetCode)
给定两个单词 word1
和 word2
,返回使得 word1
和 word2
相同所需的最小步数。
每步 可以删除任意一个字符串中的一个字符。
示例 1:
输入: word1 = "sea", word2 = "eat" 输出: 2 解释: 第一步将 "sea" 变为 "ea" ,第二步将 "eat "变为 "ea"
示例 2:
输入:word1 = "leetcode", word2 = "etco" 输出:4
一、递归搜索 + 保存计算结果 = 记忆化搜索
- 二维memo数组 存储计算过的子问题的结果
class Solution {
public:// 递归搜索 + 保存计算结果 = 记忆化搜索// 二维memo数组 存储过的子问题的结果int minDistance(string s, string t) {int m = s.size(),n = t.size(),memo[m][n]; // 二维memo数组 存储计算过的子问题的结果;memset(memo,-1,sizeof(memo));// -1 表示没有访问过function<int(int,int)> dfs = [&](int i,int j) -> int {if(i<0) //base case 当i指针越界,此时return j+1;if(j<0) //base casereturn i+1;if (memo[i][j] != -1) // memo中有当前遇到的子问题的解,直接拿来返回return memo[i][j];if (s[i] == t[j]) { memo[i][j] = dfs(i-1, j-1);} else {// memo[i][j] = min(min(dfs(i-1, j)+1,dfs(i, j-1)+1),dfs(i-1, j-1)+2);// memo[i][j] = min(dfs(i-1, j)+1,dfs(i, j-1)+1);memo[i][j] = min(dfs(i-1, j),dfs(i, j-1))+1;}return memo[i][j];};return dfs(m-1,n-1);}
}
二、动态规划 与 递归 的区别
- 递归公式
if (s[i] == t[j]) { memo[i][j] = dfs(i-1, j-1);
} else {// memo[i][j] = min(min(dfs(i-1, j)+1,dfs(i, j-1)+1),dfs(i-1, j-1)+2);// memo[i][j] = min(dfs(i-1, j)+1,dfs(i, j-1)+1);memo[i][j] = min(dfs(i-1, j),dfs(i, j-1))+1;
}
递归是自上而下调用,子问题自下而上被解决,最后解决了整个问题,而dp是从base case 出发,通过在dp数组记录中间结果,自下而上地顺序地解决子问题
- dp解法
1.确定dp数组(dp table)以及下标的含义
dp[i][j]:以i-1为结尾的字符串word1,和以j-1为结尾的字符串word2,想要达到相等,所需要删除元素的最少次数
2.确定递推公式
if (word1[i - 1] == word2[j - 1]) {dp[i][j] = dp[i - 1][j - 1];
} else {dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);
}
3.dp数组初始化
从递推式可看出,dp[i][0] 和 dp[0][j] 是一定要初始化的
- dp[i][0]:word2为字符串,以 i-1 为结尾的字符串 word1 需要删除 i 个元素才能变成空串,和word2相同
- dp[0][j]:word1为字符串,以 j-1 为结尾的字符串 word2 需要删除 j 个元素才能变成空串,和word1相同
- dp[0][0]=0,因为两个空字符串相同,删除操作为0
for(int i=1;i<=m;++i) dp[i][0] = i;
for(int j=1;j<=n;++j) dp[0][j] = j;
4.确定遍历顺序
从递推公式 dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + 1; 和dp[i][j] = dp[i - 1][j - 1]可以看出dp[i][j]都是根据正上方、正左方推出来的,所以遍历的时候一定是从上到下,从左到右,这样保证dp[i][j]可以根据之前计算出来的数值进行计算。
5.举例推导dp数组
(1)动态规划 二维dp
class Solution {
public:int minDistance(string word1, string word2) {int m = word1.size(),n = word2.size();vector<vector<int>> dp(m+1,vector<int>(n+1));for(int i=1;i<=m;++i) dp[i][0] = i;for(int j=1;j<=n;++j) dp[0][j] = j;for(int i=1;i<=m;++i) {for(int j=1;j<=n;++j) {if(word1[i-1] == word2[j-1]) dp[i][j] = dp[i-1][j-1];// else dp[i][j] = min(min(dp[i-1][j]+1,dp[i][j-1]+1),dp[i-1][j-1]+2);else dp[i][j] = min(dp[i-1][j]+1,dp[i][j-1]+1);}}return dp[m][n];}
};
- 时间复杂度: O(m * n)
- 空间复杂度: O(m * n)
(2)动态规划 二维dp 优化空间
class Solution {
public: // 动态规划 二维dp 优化空间int minDistance(string word1, string word2) {int m = word1.size(),n = word2.size();// vector<vector<int>> dp(m+1,vector<int>(n+1));vector<vector<int>> dp(2,vector<int>(n+1));for(int j=1;j<=n;++j) dp[0][j] = j;for(int i=1;i<=m;++i) {dp[i%2][0] = i;for(int j=1;j<=n;++j) {if(word1[i-1] == word2[j-1]) dp[i % 2][j] = dp[(i-1)%2][j-1];else dp[i%2][j] = min(dp[(i-1)%2][j]+1,dp[i%2][j-1]+1);}}return dp[m%2][n];}
};
- 时间复杂度: O(m * n)
- 空间复杂度: O(n)
(3)动态规划 一维dp(滚动数组) 优化空间
class Solution {
public: // 动态规划 一维dp(滚动数组) 优化空间int minDistance(string word1, string word2) {int m = word1.size(),n = word2.size();vector<int> dp(n+1);for(int j=1;j<=n;++j) dp[j] = j;for(int i=1;i<=m;++i) {// pre 代表dp[i-1][0]int pre = dp[0];// 初始化当前层的 dp[i][0]dp[0] = i;for(int j=1;j<=n;++j) {int tmp = dp[j];if(word1[i-1] == word2[j-1]) dp[j] = pre;else dp[j] = min(dp[j]+1,dp[j-1]+1);pre = tmp;}}return dp[n];}
};
- 时间复杂度: O(m * n)
- 空间复杂度: O(n)
本题除了这种解法外,还有这种解题思路:先求出最长公共子序列,然后 word1.size() + word2.size() - 两倍的最长公共子序列
求最长公共子序列,可以看我往期的这篇文章:leetCode 1143.最长公共子序列
(1)二维dp
class Solution {
public: int minDistance(string word1, string word2) {int m = word1.size(),n = word2.size();vector<vector<int>> dp(m+1,vector<int>(n+1)); for(int i=1;i<=m;++i) {for(int j=1;j<=n;++j) {if(word1[i-1] == word2[j-1]) dp[i][j] = dp[i-1][j-1] + 1;else dp[i][j] = max(dp[i-1][j],dp[i][j-1]);}}return m + n - 2 * dp[m][n];}
};
- 时间复杂度: O(m * n)
- 空间复杂度: O(m * n)
(2)二维dp:优化空间
class Solution {
public: // 方法二 二维dp 优化空间int minDistance(string word1, string word2) {int m = word1.size(),n = word2.size();vector<vector<int>> dp(2,vector<int>(n+1)); for(int i=1;i<=m;++i) {for(int j=1;j<=n;++j) {if(word1[i-1] == word2[j-1]) dp[i%2][j] = dp[(i-1)%2][j-1] + 1;else dp[i%2][j] = max(dp[(i-1)%2][j],dp[i%2][j-1]);}}return m + n - 2 * dp[m%2][n];}
};
- 时间复杂度: O(m * n)
- 空间复杂度: O(n)
(3)一维dp:优化空间
class Solution {
public:// 方法二 一维dp 优化空间int minDistance(string word1, string word2) {int m = word1.size(),n = word2.size();vector<int> dp(n+1); for(int i=1;i<=m;++i) {int pre = dp[0];for(int j=1;j<=n;++j) {int tmp = dp[j];if(word1[i-1] == word2[j-1]) dp[j] = pre + 1;else dp[j] = max(dp[j],dp[j-1]);pre = tmp;}}return m + n - 2 * dp[n];}
};
- 时间复杂度: O(m * n)
- 空间复杂度: O(n)
参考和推荐文章、视频:
代码随想录 (programmercarl.com)
动态规划之子序列,还是为了编辑距离做铺垫 | LeetCode:583.两个字符串的删除操作_哔哩哔哩_bilibili
来自代码随想录课堂的截图: