【代码随想录训练营第42期 Day45打卡 - 编辑距离问题 - LeetCode 115.不同的子序列 583. 两个字符串的删除操作 72. 编辑距离

本文主要是介绍【代码随想录训练营第42期 Day45打卡 - 编辑距离问题 - LeetCode 115.不同的子序列 583. 两个字符串的删除操作 72. 编辑距离,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

目录

一、编辑距离问题总结

二、题目与题解

题目一:115.不同的子序列

题目链接

题解:动态规划

题目二:583. 两个字符串的删除操作

题目链接

题解1:最长公共子序列变形

题解2:编辑问题模板

题目三:72. 编辑距离

题目链接

题解:动态规划

 三、小结


一、编辑距离问题总结

编辑距离问题是动态规划算法的一个重要应用,这类问题以 72. 编辑距离 - 力扣(LeetCode)最为经典。

这里对这类问题的处理做一个步骤上的小结(以 72.编辑距离为基准):

1. 定义dp数组

        定义一个二维数组dp,其中dp[i][j]表示字符串s1的前i个字符转换成字符串s2的前j个字符所需的最小编辑距离

2. dp数组状态初始化

        dp[0][j]:表示空字符串转换为s2的前j个字符,需要j次插入操作。

        dp[i][0]:表示s1的前i个字符转换为空字符串,需要i次删除操作。

3. 状态转移方程

对于dp[i][j],我们考虑s1[i-1]和s2[j-1](即s1的第i个字符和s2的第j个字符)的匹配情况:

如果匹配:s1[i-1] == s2[j-1],则无需编辑,dp[i][j] = dp[i-1][j-1]。

如果不匹配:s1[i-1] != s2[j-1],则需要考虑以下三种操作,并取最小值:

        插入:在s1中插入一个字符以匹配s2[j-1],dp[i][j] = dp[i][j-1] + 1。

        删除:删除s1中的字符s1[i-1],dp[i][j] = dp[i-1][j] + 1。

        替换:将s1中的字符s1[i-1]替换为s2[j-1],dp[i][j] = dp[i-1][j-1] + 1。

4. 遍历顺序

动态规划的顺序是从左到右,从上到下计算dp数组。 -- 正序

5. 返回结果

最终,dp[s1.length()][s2.length()]将给出将整个s1转换为s2所需的最小编辑距离。

这便是对于这类问题的小结,其实也是对于今天打卡题目 72. 编辑距离 - 力扣(LeetCode)的小结。我们在处理这类问题的时候可以按照这样的思路进行。

二、题目与题解

题目一:115.不同的子序列

题目链接

115. 不同的子序列 - 力扣(LeetCode)

给你两个字符串 s 和 t ,统计并返回在 s 的 子序列 中 t 出现的个数,结果需要对 109 + 7 取模。

示例 1:

输入:s = "rabbbit", t = "rabbit"
输出3
解释:
如下所示, 有 3 种可以从 s 中得到 "rabbit" 的方案
。
rabbbit
rabbbit
rabbbit

示例 2:

输入:s = "babgbag", t = "bag"
输出5
解释:
如下所示, 有 5 种可以从 s 中得到 "bag" 的方案。 
babgbag
babgbag
babgbag
babgbag
babgbag

提示:

  • 1 <= s.length, t.length <= 1000
  • s 和 t 由英文字母组成
题解:动态规划

这题和昨天打卡的 392. 判断子序列 - 力扣(LeetCode)相似。

昨天思路就按照上边总结的即可。

这里需要清楚状态方程的确立:

1.当前字符匹配(s[i - 1] 与 t[j - 1]),有两种情况 -- 这里求个数,即两种情况都得考虑:

                (1).不选择s[i-1],则子序列个数dp[i-1][j]

                (2).选择s[i-1],则子序列个数dp[i-1][j-1] -- 注意:这里是针对子序列的个数,而不是长度,在原有基础上多选择一个元素,个数不变,不用+1

2.当前字符不匹配,忽略(跳过)s当前字符 -- 昨天的打卡已经提到。

状态方程如下:

                if (s[i - 1] == t[j - 1]) {    dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1];}else {            //当前字符不匹配,忽略(跳过)s当前字符dp[i][j] = dp[i - 1][j];

这题还需要注意一个点:题目要求结果对 109 + 7 取模 ,但实际上最后是给数据开的足够大即可,我们对于 dp 数组开 unsigned long long 就行。

完整代码如下:

class Solution {
public:int numDistinct(string s, string t) {int n = s.size();int m = t.size();if (n < m) {          //子序列不可能长度更大return 0;}vector<vector<unsigned long long>> dp(n + 1, vector<unsigned long long>(m + 1, 0));   //dp[i][j]:以i-1为结尾的s子序列(s的前i个字符)中出现以j-1为结尾的t(t的前j个字符)的个数为dp[i][j]for(int i = 0; i <= n; i++) {       //初始化dp数组:当t为空字符串时,s中有1种方式匹配(即不选择任何字符)dp[i][0] = 1;} for(int i = 1; i <= n; i++) {for(int j = 1; j <= m; j++) {//当前字符匹配(s[i - 1] 与 t[j - 1]),有两种情况 -- 这里求个数,即两种情况都得考虑://1.不选择s[i-1],则子序列个数dp[i-1][j]//2.选择s[i-1],则子序列个数dp[i-1][j-1] -- 注意:这里是针对子序列的个数,而不是长度,在原有基础上多选择一个元素,个数不变,不用+1if (s[i - 1] == t[j - 1]) {    dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1];}else {            //当前字符不匹配,忽略(跳过)s当前字符dp[i][j] = dp[i - 1][j];}}}return dp[n][m];}
};

题目二:583. 两个字符串的删除操作

题目链接

583. 两个字符串的删除操作 - 力扣(LeetCode)

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

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

示例 1:

输入: word1 = "sea", word2 = "eat"
输出: 2
解释: 第一步将 "sea" 变为 "ea" ,第二步将 "eat "变为 "ea"

示例  2:

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

提示:

  • 1 <= word1.length, word2.length <= 500
  • word1 和 word2 只包含小写英文字母
题解1:最长公共子序列变形

我们之前打卡过 1143. 最长公共子序列 - 力扣(LeetCode) 这一道题。

而现在这一道题其实就可以理解为之前那道的变形:题目通过删除元素使两者相同且使删除的步数最少,那么我们可以发现完成删除元素之后即是原来两个字符串的最长公共子序列 -- 这时,最小步数就为:两单词的长度和 - 2 * 最长公共子序列长度。

理清题意后,这题就简单了:

class Solution {
public:int minDistance(string word1, string word2) {int n = word1.size();int m = word2.size();//求最长公共子序列长度:dp[n][m]vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));       //dp[i][j]:word1 前 i 个字符(0, i-1)与 word2 前 j 个字符(0, j-1)的最长公共子序列的长度for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; 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 n + m - 2*dp[n][m];  //最小步数:(n - dp[n][m]) + (m - dp[n][m])}
};
题解2:编辑问题模板

这里其实就跟上边总结的内容几乎一致。

需要理解的一个点就是:word1 插入一个元素等价于 word2 删除一个元素,这里只有删除操作,但完全可以按照删除和插入两个操作来看,这样基本就和总结的模板差不多。

对于字符不匹配时,有以下几种情况:

         1.删除word1[i - 1],最少操作次数为dp[i - 1][j] + 1

         2.删除word2[j - 1],最少操作次数为dp[i][j - 1] + 1

         3.同时删除word1[i - 1]和word2[j - 1],操作的最少次数为dp[i - 1][j - 1] + 2 -- 情况3实则被包含在前两种情况内,且步数更多,故取最小步数时,可以不考虑

代码如下:

class Solution {
public:int minDistance(string word1, string word2) {int n = word1.size();int m = word2.size();vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));   //dp[i][j]:以i-1为结尾(前 i 个元素)的字符串word1,和以j-1为结尾(前 j 个元素)的字符串word2,想要达到相等,所需要删除元素的最小步数//初始化dp数组for (int i = 1; i <= n; i++) {  //当word2为空字符串时,将word1转换为word2需要删除i个字符 -- 全部删掉dp[i][0] = i;}for (int j = 1; j <= m; j++) {      //当word1为空字符串时,将word2转换为word1需要删除j个字符 -- 全部删掉dp[0][j] = j;}for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; j++) {if (word1[i - 1] == word2[j - 1]) {         //当前字符匹配(相同),则无需删除dp[i][j] = dp[i - 1][j - 1];} //当前字符不匹配(不同)时,删除元素共有3种情况://  1.删除word1[i - 1],最少操作次数为dp[i - 1][j] + 1//  2.删除word2[j - 1],最少操作次数为dp[i][j - 1] + 1//  3.同时删除word1[i - 1]和word2[j - 1],操作的最少次数为dp[i - 1][j - 1] + 2 -- 情况3实则被包含在前两种情况内,且步数更多,故取最小步数时,可以不考虑else {     dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);}}}return dp[n][m];}
};

题目三:72. 编辑距离

题目链接

72. 编辑距离 - 力扣(LeetCode)

给你两个单词 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')

提示:

  • 0 <= word1.length, word2.length <= 500
  • word1 和 word2 由小写英文字母组成
题解:动态规划

这就是总结的经典模板题,重点就是删除,插入,替换三个操作的使用。

代码如下(含详细注释):

class Solution {
public:int minDistance(string word1, string word2) {int n = word1.size();int m = word2.size();vector<vector<int>> dp(n + 1,vector<int>(m + 1, 0));    //dp[i][j]:以i-1为结尾(前 i 个元素)的字符串word1,和以j-1为结尾(前 j 个元素)的字符串word2,想要达到相等,所需要操作的最小步数/*  初始化dp数组  */for (int i = 1; i <= n; i++) {              //word2为空时,word1删除当前所有元素(i)转换为word2dp[i][0] = i;}for (int j = 1; j <= m; j++) {      //word1为空时,word2删除当前所有元素(j)转换为word1dp[0][j] = j;}for (int i = 1; i <= n; i++) {for (int j = 1; j <= m; j++) {if (word1[i - 1] == word2[j - 1]) {         //当前字符匹配,则无需编辑dp[i][j] = dp[i - 1][j - 1];}/*当前字符不匹配,有三种情况:1.删除word1[i - 1],即dp[i - 1][j] + 1 -- 等价于在word2中插入元素word1[i - 1] 2.删除word2[j - 1],即dp[i][j - 1] + 1 -- 等价于在word1中插入元素word2[j - 1]3.替换word1的第i个字符 word1[i - 1] 与 word2的第j个字符 word2[j - 1],即dp[i - 1][j - 1] + 1取三者最小值,即是最小操作数*/else {dp[i][j] = min(dp[i - 1][j - 1] + 1, min(dp[i - 1][j] + 1, dp[i][j - 1] + 1));}}}return dp[n][m];}
};

 三、小结

昨天比较忙,今天是补的昨天的打卡,后边会继续坚持练习并打卡。

这篇关于【代码随想录训练营第42期 Day45打卡 - 编辑距离问题 - LeetCode 115.不同的子序列 583. 两个字符串的删除操作 72. 编辑距离的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1124990

相关文章

Linux下删除乱码文件和目录的实现方式

《Linux下删除乱码文件和目录的实现方式》:本文主要介绍Linux下删除乱码文件和目录的实现方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux下删除乱码文件和目录方法1方法2总结Linux下删除乱码文件和目录方法1使用ls -i命令找到文件或目录

Python中反转字符串的常见方法小结

《Python中反转字符串的常见方法小结》在Python中,字符串对象没有内置的反转方法,然而,在实际开发中,我们经常会遇到需要反转字符串的场景,比如处理回文字符串、文本加密等,因此,掌握如何在Pyt... 目录python中反转字符串的方法技术背景实现步骤1. 使用切片2. 使用 reversed() 函

Java操作Word文档的全面指南

《Java操作Word文档的全面指南》在Java开发中,操作Word文档是常见的业务需求,广泛应用于合同生成、报表输出、通知发布、法律文书生成、病历模板填写等场景,本文将全面介绍Java操作Word文... 目录简介段落页头与页脚页码表格图片批注文本框目录图表简介Word编程最重要的类是org.apach

Mysql实现范围分区表(新增、删除、重组、查看)

《Mysql实现范围分区表(新增、删除、重组、查看)》MySQL分区表的四种类型(范围、哈希、列表、键值),主要介绍了范围分区的创建、查询、添加、删除及重组织操作,具有一定的参考价值,感兴趣的可以了解... 目录一、mysql分区表分类二、范围分区(Range Partitioning1、新建分区表:2、分

MySQL 删除数据详解(最新整理)

《MySQL删除数据详解(最新整理)》:本文主要介绍MySQL删除数据的相关知识,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录一、前言二、mysql 中的三种删除方式1.DELETE语句✅ 基本语法: 示例:2.TRUNCATE语句✅ 基本语

C# 比较两个list 之间元素差异的常用方法

《C#比较两个list之间元素差异的常用方法》:本文主要介绍C#比较两个list之间元素差异,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录1. 使用Except方法2. 使用Except的逆操作3. 使用LINQ的Join,GroupJoin

怎样通过分析GC日志来定位Java进程的内存问题

《怎样通过分析GC日志来定位Java进程的内存问题》:本文主要介绍怎样通过分析GC日志来定位Java进程的内存问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、GC 日志基础配置1. 启用详细 GC 日志2. 不同收集器的日志格式二、关键指标与分析维度1.

Python实现对阿里云OSS对象存储的操作详解

《Python实现对阿里云OSS对象存储的操作详解》这篇文章主要为大家详细介绍了Python实现对阿里云OSS对象存储的操作相关知识,包括连接,上传,下载,列举等功能,感兴趣的小伙伴可以了解下... 目录一、直接使用代码二、详细使用1. 环境准备2. 初始化配置3. bucket配置创建4. 文件上传到os

MySQL查询JSON数组字段包含特定字符串的方法

《MySQL查询JSON数组字段包含特定字符串的方法》在MySQL数据库中,当某个字段存储的是JSON数组,需要查询数组中包含特定字符串的记录时传统的LIKE语句无法直接使用,下面小编就为大家介绍两种... 目录问题背景解决方案对比1. 精确匹配方案(推荐)2. 模糊匹配方案参数化查询示例使用场景建议性能优

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操