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

相关文章

SpringBoot+RustFS 实现文件切片极速上传的实例代码

《SpringBoot+RustFS实现文件切片极速上传的实例代码》本文介绍利用SpringBoot和RustFS构建高性能文件切片上传系统,实现大文件秒传、断点续传和分片上传等功能,具有一定的参考... 目录一、为什么选择 RustFS + SpringBoot?二、环境准备与部署2.1 安装 RustF

Python实现Excel批量样式修改器(附完整代码)

《Python实现Excel批量样式修改器(附完整代码)》这篇文章主要为大家详细介绍了如何使用Python实现一个Excel批量样式修改器,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一... 目录前言功能特性核心功能界面特性系统要求安装说明使用指南基本操作流程高级功能技术实现核心技术栈关键函

Java实现将HTML文件与字符串转换为图片

《Java实现将HTML文件与字符串转换为图片》在Java开发中,我们经常会遇到将HTML内容转换为图片的需求,本文小编就来和大家详细讲讲如何使用FreeSpire.DocforJava库来实现这一功... 目录前言核心实现:html 转图片完整代码场景 1:转换本地 HTML 文件为图片场景 2:转换 H

SpringBoot实现不同接口指定上传文件大小的具体步骤

《SpringBoot实现不同接口指定上传文件大小的具体步骤》:本文主要介绍在SpringBoot中通过自定义注解、AOP拦截和配置文件实现不同接口上传文件大小限制的方法,强调需设置全局阈值远大于... 目录一  springboot实现不同接口指定文件大小1.1 思路说明1.2 工程启动说明二 具体实施2

Vue3绑定props默认值问题

《Vue3绑定props默认值问题》使用Vue3的defineProps配合TypeScript的interface定义props类型,并通过withDefaults设置默认值,使组件能安全访问传入的... 目录前言步骤步骤1:使用 defineProps 定义 Props步骤2:设置默认值总结前言使用T

Java实现在Word文档中添加文本水印和图片水印的操作指南

《Java实现在Word文档中添加文本水印和图片水印的操作指南》在当今数字时代,文档的自动化处理与安全防护变得尤为重要,无论是为了保护版权、推广品牌,还是为了在文档中加入特定的标识,为Word文档添加... 目录引言Spire.Doc for Java:高效Word文档处理的利器代码实战:使用Java为Wo

JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法

《JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法》:本文主要介绍JavaScript中比较两个数组是否有相同元素(交集)的三种常用方法,每种方法结合实例代码给大家介绍的非常... 目录引言:为什么"相等"判断如此重要?方法1:使用some()+includes()(适合小数组)方法2

sysmain服务可以禁用吗? 电脑sysmain服务关闭后的影响与操作指南

《sysmain服务可以禁用吗?电脑sysmain服务关闭后的影响与操作指南》在Windows系统中,SysMain服务(原名Superfetch)作为一个旨在提升系统性能的关键组件,一直备受用户关... 在使用 Windows 系统时,有时候真有点像在「开盲盒」。全新安装系统后的「默认设置」,往往并不尽编

Web服务器-Nginx-高并发问题

《Web服务器-Nginx-高并发问题》Nginx通过事件驱动、I/O多路复用和异步非阻塞技术高效处理高并发,结合动静分离和限流策略,提升性能与稳定性... 目录前言一、架构1. 原生多进程架构2. 事件驱动模型3. IO多路复用4. 异步非阻塞 I/O5. Nginx高并发配置实战二、动静分离1. 职责2

Redis实现高效内存管理的示例代码

《Redis实现高效内存管理的示例代码》Redis内存管理是其核心功能之一,为了高效地利用内存,Redis采用了多种技术和策略,如优化的数据结构、内存分配策略、内存回收、数据压缩等,下面就来详细的介绍... 目录1. 内存分配策略jemalloc 的使用2. 数据压缩和编码ziplist示例代码3. 优化的