583. 两个字符串的删除操作 - 力扣(LeetCode)
给定两个单词 word1 和 word2 ,返回使得 word1 和 word2 相同所需的最小步数。
每步 可以删除任意一个字符串中的一个字符。
示例 1:
输入: word1 = "sea", word2 = "eat" 输出: 2 解释: 第一步将 "sea" 变为 "ea" ,第二步将 "eat "变为 "ea"
示例 2:
输入:word1 = "leetcode", word2 = "etco" 输出:4
一、递归搜索 + 保存计算结果 = 记忆化搜索
- 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 case
- return 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数组记录中间结果,自下而上地顺序地解决子问题
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] 是一定要初始化的
- 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数组
嘻嘻,假设我们上文这些全都不会,那我们还想要知道递推式,还想用动态规划来解决此问题,那该怎么做呢?有请各位友友继续耐心往下看~
这个表格该怎么填呢,我们先按照左边的图标注好每个格子上面的字符串,然后依次手动计算该删除的字数,填入格子里。比如"se" 和 "e" ,要相同所需的最小步数为1,"se" 和 "ea" ,要相同所需的最小步数为2,"se" 和 "eat" ,要相同所需的最小步数为3。以此类推,我们就可以得到下面的两张图的第一张表格。


观察看以上力扣给的例子所推导出的表格,看我所标注的蓝色和黄色的格子,我们可以发现有如下规律:
- 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);
(1)动态规划 二维dp
- class Solution {
- public:
- int minDistance(string word1, string word2) {
- int m = word1.size(),n = word2.size();
- 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];
- }
- };
(2)动态规划 二维dp 优化空间
- class Solution {
- public:
- // 动态规划 二维dp 优化空间
- int minDistance(string word1, string word2) {
- int m = word1.size(),n = word2.size();
- // vector
> dp(m+1,vector(n+1)); - 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];
- }
- };
(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];
- }
- };
本题除了这种解法外,还有这种解题思路:先求出最长公共子序列,然后 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
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];
- }
- };
(2)二维dp:优化空间
- class Solution {
- public:
- // 方法二 二维dp 优化空间
- int minDistance(string word1, string word2) {
- int m = word1.size(),n = word2.size();
- 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];
- }
- };
(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];
- }
- };
参考和推荐文章、视频:
动态规划之子序列,还是为了编辑距离做铺垫 | LeetCode:583.两个字符串的删除操作_哔哩哔哩_bilibili
来自代码随想录课堂的截图:
