【代码随想录训练营第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

相关文章

IDEA和GIT关于文件中LF和CRLF问题及解决

《IDEA和GIT关于文件中LF和CRLF问题及解决》文章总结:因IDEA默认使用CRLF换行符导致Shell脚本在Linux运行报错,需在编辑器和Git中统一为LF,通过调整Git的core.aut... 目录问题描述问题思考解决过程总结问题描述项目软件安装shell脚本上git仓库管理,但拉取后,上l

Python实现字典转字符串的五种方法

《Python实现字典转字符串的五种方法》本文介绍了在Python中如何将字典数据结构转换为字符串格式的多种方法,首先可以通过内置的str()函数进行简单转换;其次利用ison.dumps()函数能够... 目录1、使用json模块的dumps方法:2、使用str方法:3、使用循环和字符串拼接:4、使用字符

idea npm install很慢问题及解决(nodejs)

《ideanpminstall很慢问题及解决(nodejs)》npm安装速度慢可通过配置国内镜像源(如淘宝)、清理缓存及切换工具解决,建议设置全局镜像(npmconfigsetregistryht... 目录idea npm install很慢(nodejs)配置国内镜像源清理缓存总结idea npm in

pycharm跑python项目易出错的问题总结

《pycharm跑python项目易出错的问题总结》:本文主要介绍pycharm跑python项目易出错问题的相关资料,当你在PyCharm中运行Python程序时遇到报错,可以按照以下步骤进行排... 1. 一定不要在pycharm终端里面创建环境安装别人的项目子模块等,有可能出现的问题就是你不报错都安装

Java集合之Iterator迭代器实现代码解析

《Java集合之Iterator迭代器实现代码解析》迭代器Iterator是Java集合框架中的一个核心接口,位于java.util包下,它定义了一种标准的元素访问机制,为各种集合类型提供了一种统一的... 目录一、什么是Iterator二、Iterator的核心方法三、基本使用示例四、Iterator的工

Java 线程池+分布式实现代码

《Java线程池+分布式实现代码》在Java开发中,池通过预先创建并管理一定数量的资源,避免频繁创建和销毁资源带来的性能开销,从而提高系统效率,:本文主要介绍Java线程池+分布式实现代码,需要... 目录1. 线程池1.1 自定义线程池实现1.1.1 线程池核心1.1.2 代码示例1.2 总结流程2. J

idea突然报错Malformed \uxxxx encoding问题及解决

《idea突然报错Malformeduxxxxencoding问题及解决》Maven项目在切换Git分支时报错,提示project元素为描述符根元素,解决方法:删除Maven仓库中的resolv... 目www.chinasem.cn录问题解决方式总结问题idea 上的 maven China编程项目突然报错,是

使用Java填充Word模板的操作指南

《使用Java填充Word模板的操作指南》本文介绍了Java填充Word模板的实现方法,包括文本、列表和复选框的填充,首先通过Word域功能设置模板变量,然后使用poi-tl、aspose-words... 目录前言一、设置word模板普通字段列表字段复选框二、代码1. 引入POM2. 模板放入项目3.代码

Python爬虫HTTPS使用requests,httpx,aiohttp实战中的证书异步等问题

《Python爬虫HTTPS使用requests,httpx,aiohttp实战中的证书异步等问题》在爬虫工程里,“HTTPS”是绕不开的话题,HTTPS为传输加密提供保护,同时也给爬虫带来证书校验、... 目录一、核心问题与优先级检查(先问三件事)二、基础示例:requests 与证书处理三、高并发选型:

Linux命令rm如何删除名字以“-”开头的文件

《Linux命令rm如何删除名字以“-”开头的文件》Linux中,命令的解析机制非常灵活,它会根据命令的开头字符来判断是否需要执行命令选项,对于文件操作命令(如rm、ls等),系统默认会将命令开头的某... 目录先搞懂:为啥“-”开头的文件删不掉?两种超简单的删除方法(小白也能学会)方法1:用“--”分隔命