代码随想录刷题笔记 Day 59 | 两个字符串的删除操作 No.583 | 编辑距离 No.72

文章目录

    • [Day 59](#Day 59)
      • [01. 两个字符串的删除操作(No. 583)](#01. 两个字符串的删除操作(No. 583))
        • [<1> 题目](#<1> 题目)
        • [<2> 题解](#<2> 题解)
        • [<3> 代码](#<3> 代码)
      • [02. 编辑距离(No. 72)](#02. 编辑距离(No. 72))
        • [<1> 题目](#<1> 题目)
        • [<2> 题解](#<2> 题解)
        • [<3> 代码](#<3> 代码)

Day 59

01. 两个字符串的删除操作(No. 583)

题目链接

代码随想录题解

<1> 题目

给定两个单词 word1word2 ,返回使得 word1word2 相同 所需的最小步数

每步 可以删除任意一个字符串中的一个字符。

示例 1:

输入: word1 = "sea", word2 = "eat"

输出: 2

解释: 第一步将 "sea" 变为 "ea" ,第二步将 "eat "变为 "ea"

示例 2:

输入:word1 = "leetcode", word2 = "etco"

输出:4

提示:

  • 1 <= word1.length, word2.length <= 500
  • word1word2 只包含小写英文字母、
<2> 题解

本题问的是给定两个单词,对两个单词分别进行删除操作,问最终将它们删成相同的单词最少需要多少次。

为了使得删除的最少,也就是保留下来的最多;那什么时候能保证两个单词都保留的最多呢?那就是将两个单词都删成它们的公共子序列的情况,比如题目中的案例1:

删除完成后,得到的就是他们的公共子序列。

那现在这个题就很轻松了,关于子序列的题上面已经做过很多遍了,这里简单的回顾一下:

关于 dp 数组的定义:将 dp[i][j] 定义为单词1 从 0 到 i 与单词2 从 0 到 j最长的公共子序列的长度。

当发现 nums[i] == nums[j],说明这个节点可以被取得,所得的结果累加到上个状态,最终得到的公式为

java 复制代码
dp[i][j] = dp[i - 1][j - 1] + 1

当发现 nums[i] != nums[j] 的时候,说明这个节点是不能被取得的,那此时的长度就是

java 复制代码
Math.max(dp[i - 1][j] , dp[i][j - 1]);

这样得出它们最长公共子序列的长度,再算出需要删除的次数即可。

<3> 代码
java 复制代码
class Solution {
    public int minDistance(String word1, String word2) {
        int[][] dp = new int[word1.length() + 1][word2.length() + 1];
        for (int i = 1; i < dp.length; i++) {
            for (int j = 1; j < dp[0].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]);
                }
            }
        }
        int num = dp[word1.length()][word2.length()];
        return word1.length() + word2.length() - 2*num;
    }
}

02. 编辑距离(No. 72)

题目链接

代码随想录题解

<1> 题目

给你两个单词 word1word2请返回将 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')

提示:

  • 0 <= word1.length, word2.length <= 500
  • word1word2 由小写英文字母组成
<2> 题解

本题看似有三种操作,其实真实做题的时候为了方便可以只考虑两种,删除和替换的操作;比如说上面的案例2的最后一步,其实是 exection 插入了 u,这其实和 execution 删除 u 达到的效果和记录的步数 都是相同的。

将三种操作缩减成两种操作那解答起来就容易一些了。

1. 状态

本题的状态其实是比较好确认的,就是将 一定长度的 单词1 和 单词2 变成相同的单词最少需要多少步的操作,这种建立一个二维的状态来针对两个数组的题目上面已经做过非常多了。

2. dp 数组

本题仍然采用后移一位的方式来建立 dp 数组,也就是 dp[i][j] 代表将 i - 1 长度的单词 1 和 j - 1 长度的单词 2 变为同一个单词需要的最少的步数,这样来声明 dp 数组主要是为了初始化比较简单。

3. 状态转移方程

对于一个节点有三种状态:

  • 第一种就是 text[i] == text[j] 也就是这两个节点相同,不需要做任何的操作。
  • 第二类是当它们两个不相同的时候,为了得到两个相同的数组,就需要对 其中一个数组做 删除或者替换的操作
    • 将节点删除的状态
    • 替换节点的状态

先来看第一种,当两个节点相同的时候,说明不需要对其做任何的操作

比如上图的情况,这里发现两个 a 是相同的,此时要想让这两个字符串相同,就需要绿色部分也相同,也就是要加上使得绿色部分相同的操作的次数,这个次数正是 dp[i - 1][j - 1]

再来看第二种情况,就是当这两个节点不相同的时候:

那将这两个字符串变成相同的就有替换和删除两种方法,如果是要删除的话就是不考虑这个节点了

  • 如果是要删除上面的 b,那此时就会得到这样的情况:

    为了让删除 b 之后得到的字符串是相同的,就需要得到 dp[i][j - 1] 的值,也就是删除 b 节点,剩余的部分与单词 1 要相同需要走多少步

  • 如果要删除上面的 a 的话,就是如下的情况:

    那次是需要的就是 dp[i - 1][j],但因为做了删除的操作,所以需要在这个操作上累加一个 1

再来讨论删除替换节点的情况,也就是比如将 a 替换为 b(也可以将 b 替换为 a,结果是相同的)

那次是需要的就是将绿色部分保持为相同需要的次数,那就是 dp[i - 1][j - 1],在这个基础上再累加一个 1,再与上面删除的操作取一个 最小值,最终得到如下的递推公式

java 复制代码
dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);
dp[i][j] = Math.min(dp[i][j], dp[i - 1][j - 1] + 1);

4. 初始化

初始化的时候一定要看 dp 数组和递推公式依赖于哪些节点

dp数组的定义: dp[i][j] 代表将 i - 1 长度的单词 1 和 j - 1 长度的单词 2 变为同一个单词需要的最少的步数

同时观察到递推公式依赖的是左上角,也就是此时要初始化第一行和第一列:第一行的含义是 长度为 0 的字符串1和字符串2变为相同需要多少次,让一个有长度的单词变为长度为 0,那就只有一种删除操作了,需要的次数是单词的长度次;第一列同理。

java 复制代码
for (int i = 1; i < dp.length; i++) {
    dp[i][0] = i;
}
for (int j = 1; j < dp[0].length; j++) {
    dp[0][j] = j;
}

写出最终的代码

<3> 代码
java 复制代码
class Solution {
    public int minDistance(String word1, String word2) {
        char[] c1 = word1.toCharArray(); // 得到字符数组,便于操作
        char[] c2 = word2.toCharArray();
        int[][] dp = new int[c1.length + 1][c2.length + 1];
        for (int i = 1; i < dp.length; i++) {
            dp[i][0] = i;
        }
        for (int j = 1; j < dp[0].length; j++) {
            dp[0][j] = j;
        }
        for (int i = 1; i < dp.length; i++) {
            for (int j = 1; j < dp[0].length; j++) {
                if (c1[i - 1] == c2[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);
                    dp[i][j] = Math.min(dp[i][j], dp[i - 1][j - 1] + 1);
                }
            }
        }
        return dp[c1.length][c2.length];
    }
}
相关推荐
shuangrenlong6 分钟前
slice介绍slice查看器
java·ubuntu
牧竹子6 分钟前
对原jar包解压后修改原class文件后重新打包为jar
java·jar
Microsoft Word15 分钟前
c++基础语法
开发语言·c++·算法
数据小爬虫@17 分钟前
如何利用java爬虫获得淘宝商品评论
java·开发语言·爬虫
Natural_yz18 分钟前
大数据学习17之Spark-Core
大数据·学习·spark
喜欢猪猪19 分钟前
面试题---深入源码理解MQ长轮询优化机制
java
qq_1728055925 分钟前
RUST学习教程-安装教程
开发语言·学习·rust·安装
天才在此28 分钟前
汽车加油行驶问题-动态规划算法(已在洛谷AC)
算法·动态规划
一只小小汤圆40 分钟前
opencascade源码学习之BRepOffsetAPI包 -BRepOffsetAPI_DraftAngle
c++·学习·opencascade
醉陌离42 分钟前
渗透测试笔记——shodan(4)
笔记