Day47 力扣动态规划 :583. 两个字符串的删除操作|72. 编辑距离 | 编辑距离总结篇

Day47 力扣动态规划 :583. 两个字符串的删除操作|72. 编辑距离 | 编辑距离总结篇

  • 583. 两个字符串的删除操作
    • 第一印象
      • 递推公式
    • 看完题解的思路
    • 实现中的困难
    • 感悟
    • 代码
  • 72. 编辑距离
    • 第一印象
    • 看完题解的思路
    • 实现中的困难
    • 感悟
    • 代码
  • 编辑距离总结篇
    • 判断子序列
    • 不同的子序列
    • 两个字符串的删除操作
    • 编辑距离

583. 两个字符串的删除操作

本题和动态规划:115.不同的子序列 相比,其实就是两个字符串都可以删除了,情况虽说复杂一些,但整体思路是不变的。
https://programmercarl.com/0583.%E4%B8%A4%E4%B8%AA%E5%AD%97%E7%AC%A6%E4%B8%B2%E7%9A%84%E5%88%A0%E9%99%A4%E6%93%8D%E4%BD%9C.html

第一印象

我觉得这道题就是能模拟删除两个串的情况,我直接开做!

我做出来了!无敌了

dp数组的含义还是之前的,到i-1 j-1的时候需要删除多少步。

难在递推公式

递推公式

元素相同的时候,肯定就不用删除了,那么就是和没有这两个元素的时候删的步骤是一样的。dp[i][j] = dp[i-1][j-1]

元素不同的时候,那么肯定是要删除的。不管 s 还是 t,先删除一个。

比如s: aeb 和t: abe。

b 和 e 不相等。我肯定要删一个,如果删了 b 就是看 ae 和 abe 若要相等需要多少步,若删了 e 是看 ab 和 aeb 若要相等要多少步。选最小的那个,再+1就行了。 这个例子里面两个都是 1 ,1+1 = 2了。

所以就是删除了 s 的,就是模拟 s 删除元素dp[i-1][j];删除了 t 的,就是模拟 t 删除元素的dp[i][j -1]。

题目要求最少删除的步数,选这个两个最小的那个 再加上一开始删除的一个就行了。

dp[i][j] = Math.min(dp[i - 1][j], dp[i][j - 1]) + 1;

看完题解的思路

实现中的困难

我觉得难在思考初始化的问题,弄明白递推公式的思路之后,这道题的初始化其实是可以从 dp 公式去理解的。

比如 s 是空, t有多少个元素就该删除多少个。 t 是空的话,s有多少个元素就该删除多少个。

然后在画图模拟的时候看这个对不对。

感悟

我有点明白模拟删除元素的意思了,这道题的逻辑比较清晰

我觉得dp问题如果能想明白递推公式的逻辑,就比较能自己做出来了。

代码

class Solution {
    public int minDistance(String word1, String word2) {
        //dp
        int[][] dp = new int[word1.length() + 1][word2.length() + 1];
        //init
        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;
        }

        //func
        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], dp[i][j - 1]) + 1;
                }
            }
        }
        return dp[word1.length()][word2.length()];
    }
}

72. 编辑距离

最终我们迎来了编辑距离这道题目,之前安排题目都是为了 编辑距离做铺垫。

https://programmercarl.com/0072.%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB.html

第一印象

我觉得模拟删除理解了一些

但这道题又让我添加、又可以替换

一下子就不知道该干什么了,而且会不会有替换其实比删除步骤更少的可能呢?这样又该怎么处理呢?

直接看题解了。

看完题解的思路

难在递推公式,别的不说了。

如果两个元素一样,一样了就不需要有新的步骤了。就是没有这个两个元素的时候,让两个字符串一样需要的最少步骤数,也就是dp[i-1][j-1]。这样dp[i][j] 就是 现在情况下,让两个字符串一样需要的最少步骤。

如果两个元素不一样,一共三种处理方式

  • 添加
  • 删除
  • 替换

添加和删除其实是一样的,比如 ac 和 abec。我是把abec删除be,还是把ac添加be其实都一样。

而只考虑删除的话,就是上面的583题了。选dp[i-1][j] + 1 和 dp[i][j-1] + 1最小的那个。

接下来是替换。替换之后就是两个一样的字符串了,比如拿到的 abe 和 abc。

b 和 e 不一样,只需要换一下就一样了。所以这个最小步骤数就是 不拿来 b 和 e的时候,让两个字符串相同的最小步骤数 + 1(把b换成e或者e换成b)。也就是dp[i-1][j-1] + 1

或者从另一个角度理解,替换之后相当于拿来了两个一样的字母对吧。也就是回到两个元素一样的情况,也就是dp[i-1][j-1] ,但因为多了一步替换,所以就是dp[i-1][j-1] + 1.

实现中的困难

思路清晰就没困难

感悟

感觉难在理解这个过程,要把 dp[i][j] 当做: 到i-1 j-1的情况,让这俩串一样需要的最小步骤。

所以每次依赖前面的 dp 来推到现在的 dp的时候,前面dp记录的情况是 前面元素都已经一样了,所以才只是考虑新拿来的两个元素一样或者不一样该怎么推导出现在的 dp

这样我理解起来比较舒服

但是感觉编辑距离问题 我还没有完全完全明白。

代码

class Solution {
    public int minDistance(String word1, String word2) {
        //dp
        int[][] dp = new int[word1.length() + 1][word2.length() + 1];
        //init
        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;
        }
        //func
        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(Math.min(dp[i - 1][j], dp[i][j - 1]) + 1, dp[i - 1][j - 1] + 1);
                }
            }
        }
        return dp[word1.length()][word2.length()];
    }
}

编辑距离总结篇

做一个总结吧
https://programmercarl.com/%E4%B8%BA%E4%BA%86%E7%BB%9D%E6%9D%80%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB%EF%BC%8C%E5%8D%A1%E5%B0%94%E5%81%9A%E4%BA%86%E4%B8%89%E6%AD%A5%E9%93%BA%E5%9E%AB.html

直接看卡哥的总结吧

判断子序列

题目:给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

本题只有删除

  • if (s[i - 1] == t[j - 1])
    • t中找到了一个字符在s中也出现了
  • if (s[i - 1] != t[j - 1])
    • 相当于t要删除元素,继续匹配

为什么不删除 s 呢? 因为判断都是 s 是不是 t的子序列。最后比较的是公共子序列的最大长度是不是 s 的长度。如果删除 s 的,其实没什么意义。

但选取 删s和删t的 min,也是能做对的。因为这个递推公式求的是最长公共子序列长度。

如果不理解为什么看一眼删除 s ,再看一眼删除 t,明明外层for循环是 s 啊。对我而言也要明确:当我看 i j的时候,i j-1和i-1 j和i-1 j-1 都是已经遍历过的, 我有时候想不清楚这个从而迷糊。

也要去联想我自己想明白的两个字符串的删除操作的删除过程,就理解一些了。

//func
if (s[i - 1] == t[j - 1]) dp[i][j] = dp[i - 1][j - 1] + 1;
else dp[i][j] = dp[i][j - 1];

不同的子序列

题目:给定一个字符串 s 和一个字符串 t ,计算在 s 的子序列中 t 出现的个数。

本题虽然也只有删除操作,不用考虑替换增加之类的

当s[i - 1] 与 t[j - 1]相等时,dp[i][j]可以有两部分组成。

一部分是用s[i - 1]来匹配,那么个数为dp[i - 1][j - 1]。

一部分是不用s[i - 1]来匹配,个数为dp[i - 1][j]。

这里可能有同学不明白了,为什么还要考虑 不用s[i - 1]来匹配,都相同了指定要匹配啊。

例如: s:bagg 和 t:bag ,s[3] 和 t[2]是相同的,但是字符串s也可以不用s[3]来匹配,即用s[0]s[1]s[2]组成的bag。

当然也可以用s[3]来匹配,即:s[0]s[1]s[3]组成的bag。

这里也是,为什么不用看:不用t[j - 1]来匹配,个数为dp[i][j - 1] 这个情况呢? 因为题目要看的是 s 的子序列中 t 出现的个数。

  • 如果用 s 的 i-1 来匹配。就是 0~i-2 的 s 中出现的 0~j-2的 t 的个数 就是 0~i-1 的 s 中出现的 0~j-1 的 t 的个数。
  • 所以如果不用 s 的 i -1 来匹配。就是 0~i-2 的 s 中出现的现在的 0~j-1 的 t 的个数。
  • 这两部分的和才是 到 i-1 j-1 的时候,s 中有多少个 t 的个数。

所以当s[i - 1] 与 t[j - 1]相等时,dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];

而当s[i - 1] 与 t[j - 1]不相等时,dp[i][j]只有一部分组成,不用s[i - 1]来匹配,即:dp[i - 1][j]

所以递推公式为:dp[i][j] = dp[i - 1][j];

//func
if (s[i - 1] == t[j - 1]) {
    dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
} else {
    dp[i][j] = dp[i - 1][j];
}

两个字符串的删除操作

我的拿手好题,这道题的描述很直接,就是删除,所以一下子明白了模拟删除的过程

编辑距离

这两道题都是这个文章里写过的,就不再写一遍了。

哪里不清楚就是看卡哥的题解吧。

你可能感兴趣的:(leetcode,动态规划,算法)