代码随想录算法训练营第60期第四十九天打卡

       大家好,今天我们还是继续我们的动态规划章节,可能有的朋友已经开始厌倦了说为什么动态规划怎么这么多题目,大家可以想想我们前面其实刷过好几种类型的动态规划的经典题目比如说各式各样的背包问题当然包括0-1背包,完全背包,大家也知道这两种背包问题的差异就在于物品是否可以选择多次,后面我们还刷了打家劫舍问题,当然还有比较难的买股票的最佳时期问题,前天我们也开始我们的子序列问题,那我们今天还有一种比较重要的问题就是编辑距离问题,那我们就一起走进我们今天的题目。

第一题对应力扣编号为115的题目不同的子序列

       其实大家看到这道题的时候大家应该会想起我们昨天刷过一道题目叫做判断子序列,其实我可以告诉大家,这道题要比那道题难一些,我们来看看这道题的题目要求:

        看完题目我们明白了,其实跟那道题有点类似的地方就是都涉及到了子序列的问题,但是这道题我们是需要求出s的子序列中t出现的次数,这个其实就有难度了,当然我们也知道本题目应该是可以使用动态规划的思路来解题的,那我们就尝试使用动规五部曲来解决这道题目:

       第一步确定dp数组以及下标的含义,dp[i][j]:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数为dp[i][j]。我们前面有道题说过为了初始化方便我们是i-1而不是i。

        第二步确定递推公式,感觉这个就有难度了,其实我们做的题目多了就会有感觉,我们应该还是需要看对应位置上的元素是否相等,第一种情况是s[i - 1] 与 t[j - 1]相等,第二种情况是s[i - 1] 与 t[j - 1] 不相等,当s[i - 1] 与 t[j - 1]相等时,dp[i][j]可以有两部分组成。一部分是用s[i - 1]来匹配,那么个数为dp[i - 1][j - 1]。即不需要考虑当前s子串和t子串的最后一位字母,所以只需要 dp[i-1][j-1]。还有一种情况就是一部分是不用s[i - 1]来匹配,个数为dp[i - 1][j]。另一种情况就是s[i - 1] 与 t[j - 1]不相等时,我们这时候其实相当于dp[i][j]只有一部分组成,不用s[i - 1]来匹配(就是模拟在s中删除这个元素),即:dp[i - 1][j]。

       第三步是dp数组的初始化,从递推公式dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]; 和 dp[i][j] = dp[i - 1][j]; 中可以看出dp[i][j] 是从上方和左上方推导而来,那么 dp[i][0] 和dp[0][j]是一定要初始化的。那其实也很明显,我们的dp[i][0]应该初始化为1,而且我们的dp[0][j]应该初始化为0,但是大家要注意我们dp[0][0]应该是初始化为1,因此我们dp[0][j]的初始化应该是从1开始初始化为0.

       第四步就是遍历顺序的确定,其实很明显就是从前往后,这个就无需多说了,其实严格来说应该是左上方和正上方推出来的。

      根据以上分析我们可以给出代码:

class Solution {
public:int numDistinct(string s, string t) {//初始化dp数组vector<vector<uint64_t>> dp(s.size() + 1, vector<uint64_t>(t.size() + 1));//dp数组初始化for (int i = 0; i <= s.size(); ++i) dp[i][0] = 1;for (int j = 1; j <= t.size(); ++j) dp[0][j] = 0;for (int i = 1; i <= s.size(); ++i){for (int j = 1; j <= t.size(); ++j){//如果相同存在两种情况第一种用最后一个元素第二种不用最后一个元素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];}}}return dp[s.size()][t.size()];}
};

       其实本题的难点就在于理解状态转移方程,我们本题的dp数组的含义其实还是比较抽象的,我们要比较对应位置的元素是否相等,如果相等的话我们应该如何操作,如果不相等的话我们应该如何操作。其实我一想了好久才大致明白我们的状态转移,我开始也思考了为什么我们不考虑用不用t[j-1]后来发现是因为我没有仔细审题,题目问的是s中有多少个t,所以重点考虑的应该是s[i-1],还有如果我们对应的元素不相等,其实我们相当于模拟删除s里面的s[i-1]因此我们这里应该是dp[i-1][j],相等的话我们则不需要考虑两个字符串的最后一个元素,但是不相等的话我们的确是需要考虑。这点大家要多思考。

第二题对应力扣编号为583的题目两个字符串的删除操作

        我们来到今天的第二题,第一题真给我差点整不会了,大家务必要仔细思考一下,我们来看今天的第二题是什么意思:

       其实题目理解起来并不算难,就是我们要使得两个字符串相同我们就需要进行增加删除操作,我们需要求我们最少的操作数是多少,我们还是得使用动规五部曲的思路来解决这道题目:

        第一步确定dp数组以及下标的含义,我们应该是dp[i][j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数。这个定义起来其实不难,但是难的应该是递推公式。

        第二步确定递推公式,同样我们还是得考虑对应位置的元素是否相等,也就是当word1[i - 1] 与 word2[j - 1]相同的时候,和当word1[i - 1] 与 word2[j - 1]不相同的时候,先来看第一种情况就是对应位置元素相同的时候,当word1[i - 1] 与 word2[j - 1]相同的时候,dp[i][j] = dp[i - 1][j - 1],这个其实好理解,对应位置相同我们这里的元素无需操作只需看前面即可,但是比较难的是第二种情况,这个其实又有好几种情况,情况一:删word1[i - 1],最少操作次数为dp[i - 1][j] + 1,删word2[j - 1],最少操作次数为dp[i][j - 1] + 1,同时删word1[i - 1]和word2[j - 1],操作的最少次数为dp[i - 1][j - 1] + 2,那最后当然是取最小值,所以当word1[i - 1] 与 word2[j - 1]不相同的时候,递推公式:dp[i][j] = min({dp[i - 1][j - 1] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1}),其实这里想一次性想得很全面我个人感觉是比较难的。

        第三步dp数组的初始化,从递推公式中,可以看出来,dp[i][0] 和 dp[0][j]是一定要初始化的。但其实似乎又不难理解,dp[i][0]:word2为空字符串,以i-1为结尾的字符串word1要删除多少个元素,才能和word2相同呢,很明显dp[i][0] = i。dp[0][j]的话同理,不应该是删除j个因此dp[0][j]=j,这样我们的初始化就完成了。

        第四步确定遍历顺序,这个其实也毫无疑问,从递推公式 dp[i][j] = min(dp[i - 1][j - 1] + 2, min(dp[i - 1][j], dp[i][j - 1]) + 1); 和dp[i][j] = dp[i - 1][j - 1]可以看出dp[i][j]都是根据左上方、正上方、正左方推出来的。

        那通过以上分析我们就可以尝试给出本题的解题代码:

class Solution {
public:int minDistance(string word1, string word2) {//定义dp数组vector<vector<int>> dp(word1.size() + 1,vector<int>(word2.size() + 1));//dp数组的初始化for (int i = 0; i <= word1.size(); ++i) dp[i][0] = i;for (int j = 0; j <= word2.size(); ++j) dp[0][j] = j; for (int i = 1; i <= word1.size(); ++i){for (int j = 1; j <= word2.size(); ++j){if (word1[i - 1] == word2[j - 1]) dp[i][j] = dp[i - 1][j - 1];else{dp[i][j] = min(dp[i - 1][j] + 1, min(dp[i][j - 1] + 1, dp[i - 1][j - 1] + 2));}}}return dp[word1.size()][word2.size()];}
};

        其实本题的难点在于考虑的情况是否周全,还是思路要理清。接下来就是重头戏编辑距离题目,我们这道题目就分享到这里。

第三题对应力扣编号为72的题目编辑距离

        在刷题过程中我似乎记得我们前面刷的题都是为这个题目服务的,其实上一个题目我们只涉及到了字符串的删除没有涉及到替换添加等操作,那我们就一起来看看这道题目的具体要求:

       我们其实是求将前面的字符串变成后面字符串的的最少操作数,当然本题目我们可以进行替换删除插入操作,而我们前面的题目只能进行删除操作,那我们需要考虑的情况可能就更多了,我们就使用动规五部曲来解决一下这道题目:

       第一步就是确定 确定dp数组以及下标的含义,其实这个似乎不难,我们完全有经验,dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]。跟我们以前的题目定义dp数组的思路是完全类似的。

       第二步就是确定递推公式,这个或许有难度,这个题如果我们要操作的话我们就必须考虑插入删除替换等各种操作,如果对应元素相同那么就不需要任何的操作,但是如果不同的话就麻烦了,其实有如下几个操作,操作一:word1删除一个元素,那么就是以下标i - 2为结尾的word1 与 j-1为结尾的word2的最近编辑距离 再加上一个操作。操作二:word2删除一个元素,那么就是以下标i - 1为结尾的word1 与 j-2为结尾的word2的最近编辑距离 再加上一个操作。这里就有问题了为什么都是删除元素为什么没有添加元素的操作,其实大家可以变相思考一下,我word2添加一个元素,相当于word1删除一个元素,比如说word1 = "ad", word2 = "a",那我word1删除一个"d"变成"a",与word2添加一个"d",变成"ad"的最后的操作数应该是一样的,操作三:替换元素,这个其实word1替换word1[i - 1],使其与word2[j - 1]相同, 那么只需要一次替换的操作,就可以让 word1[i - 1] 和 word2[j - 1] 相同,因此这时候dp[i][j] = dp[i-1][j - 1] +1,那这样我们的递推公式就知道了,就是分为对应元素相等跟不相等的情况。

      第三步就是dp数组的初始化,应该重点思考dp[i][0] 和 dp[0][j] 表示什么?其实也不难,dp[i][0]就应该是i,对word1里的元素全部做删除操作,即:dp[i][0] = i; 同理dp[0][j] = j; 这个其实我们上一道题目就涉及到了。

      第四步就是遍历顺序,其实我们也不多说了跟上一道题目是完全一样的。

      这样经过我们的分析我们就给出本题的解题代码:

class Solution {
public:int minDistance(string word1, string word2) {//初始化dp数组vector<vector<int>> dp(word1.size() + 1, vector<int>(word2.size() + 1));//dp数组初始化for (int i = 0; i <= word1.size(); ++i) dp[i][0] = i;for (int j = 0; j <= word2.size(); ++j) dp[0][j] = j;for (int i = 1; i <= word1.size(); ++i){for (int j = 1; j <= word2.size(); ++j){if (word1[i - 1] == word2[j - 1]) dp[i][j] = dp[i - 1][j - 1];else dp[i][j] = min(dp[i - 1][j] + 1, min(dp[i][j - 1] + 1, dp[i - 1][j - 1] + 1));}}return dp[word1.size()][word2.size()];}
};

        这道题目很有趣,完全就是动态规划的典型例题,大家一定要把这道题目掌握扎实,我感觉好多公司面试都会考察这道题目,其实我给大家一个总结大家重点首先理解dp数组,其次搞清楚我们需要什么样的操作就可以了,有的我们只可以进行删除操作,有的我们增删改查都可以,而且大家要明白每一种操作我们的状态转移方程应该如何表示就可以,其实我们前面的几道题都是为了编辑距离这道题目服务的,可是说是绝杀。大家务必学明白!!

今日总结

        我们即将到达动态规划的终结篇了,我们还有单调栈跟图论我们的训练营就结束了,大家收获一定是满满的,我们在众多章节收获了很多,大家需要二刷甚至三刷才可以,重要的是理解思想,多去思考多去尝试就可以,我们今天的分享就到这里,大家明天见!

       

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.pswp.cn/web/81458.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

centos7.9离线升级内核到4.19.12详细教程

cenots7.9默认安装的内核版本是:3.10.0-1160.119.1.el7.x86_64,在安装nvidia显卡驱动的时候,提示内核版本过低,需要升级内核版本,升级完成之后,就可以顺利的安装上nvidia显卡驱动了,实测有效。 一、查看当前内核版本命令 uname -r二、下载离线内核的rpm包

Vue3 + TypeScript + el-input 实现人民币金额的输入和显示

输入人民币金额的参数要求&#xff1a; 输入要求&#xff1a; 通过键盘&#xff0c;只允许输入负号、小数点、数字、退格键、删除键、方向左键、方向右键、Home键、End键、Tab键&#xff1b;负号只能在开头&#xff1b;只保留第一个小数点&#xff1b;替换全角输入的小数点&a…

方正字库助力华为,赋能鸿蒙电脑打造全场景字体解决方案

2025年5月19日&#xff0c;搭载华为鸿蒙操作系统的鸿蒙电脑&#xff0c;面向用户推出集AI智能、互联流畅、安全保障和精致体验于一体的全新办公系统。作为鸿蒙生态核心字体服务商&#xff0c;方正字库为此次提供了全面的系统字体支持&#xff0c;涵盖中文、西文及符号三大类字库…

PHPStudy 一键式网站搭建工具的下载使用

目录 一、下载 PHPStudy二、安装步骤三、基本使用方法3.1 创建网站3.2 管理数据库3.3 软件管理3.4 自动启动3.5 配置管理 四、注意事项和进阶使用4.1 注意事项4.2 进阶使用 背景&#xff1a; 我们在学习和工作中&#xff0c;经常会遇到各种需要自己搭建环境的场景&#xff0c;这…

java中的线程安全的集合

1.ConcurrentHashMap。 key,value结构。 jdk1.7通过分段锁保证不同段同时操作是线程安全的&#xff0c;但并发不足&#xff0c;jdk1.8通过node节点锁和CAS保证并发安全。不同node节点可以并发读写。通过它的computer,computerIfAbsent,等可以保证原子更新value。ifAbsent表示有…

MySQL问题:MySQL中使用索引一定有效吗?如何排查索引效果

不一定有效&#xff0c;当查询条件中不包含索引列或查询条件复杂且不匹配索引顺序 对于一些小表&#xff0c;MySQL可能选择全表扫描而非使用索引&#xff0c;因为全表扫描的开销可能更小 最终是否用上索引是根据MySQL成本计算决定的&#xff0c;评估CPU和I/O成本 排查索引效…

使用vscode MSVC CMake进行C++开发和Debug

使用vscode MSVC CMake进行C开发和Debug 前言软件安装安装插件构建debuug方案一debug方案二其他 前言 一般情况下我都是使用visual studio来进行c开发的&#xff0c;但是由于python用的是vscode&#xff0c;所以二者如果统一的话能稍微提高一点效率。 软件安装 需要安装的软…

【后端高阶面经:消息队列篇】29、Kafka高性能探秘:零拷贝、顺序写与分区并发实战

一、 顺序写入:磁盘性能的极致挖掘 Kafka的高性能本质上源于对磁盘顺序访问的深度优化。 传统随机写入的磁盘操作需要磁头频繁寻道,机械硬盘的随机写性能通常仅为100IOPS左右,而Kafka通过追加日志(Append-Only Log)模式,将所有消息按顺序写入分区文件,使磁盘操作转化为…

AI预测3D新模型百十个定位预测+胆码预测+去和尾2025年5月27日第90弹

从今天开始&#xff0c;咱们还是暂时基于旧的模型进行预测&#xff0c;好了&#xff0c;废话不多说&#xff0c;按照老办法&#xff0c;重点8-9码定位&#xff0c;配合三胆下1或下2&#xff0c;杀1-2个和尾&#xff0c;再杀6-8个和值&#xff0c;可以做到100-300注左右。 (1)定…

Git 初次推送远程仓库

Git 初次推送远程仓库&#xff08;完整实战版&#xff09; —— 涵盖重命名分支、强制合并、冲突解决等高频场景 &#x1f525; 核心流程图 初始化 → 关联远程 → 提交代码 → 处理分支冲突 → 成功推送 1. 基础操作&#xff08;全新仓库&#xff09; # 初始化 cd /your/pr…

Pluto实验报告——基于FM的音频信号传输并解调恢复

目录 一、实验目的 ................................ ................................ ................................ .................. 3 二、实验内容 ................................ ................................ ................................ ......…

输出数据OutputFormat案例

输出数据OutputFormat 案例&#xff1a; www.atguigu.com www.atguigu.com www.atguigu.com www.hao123.com www.shouhu.com www.baidu.com www.atguigu.com www.qq.com www.gaga.com www.qinghua.com www.sogou.com www.baidu.com www.alibaba.com …

STM32与ESP32的区别

STM32与ESP32都是当前电子行业中广泛使用的微控制器芯片&#xff0c;但二者在架构、功能、应用领域以及开发生态上均存在显著差异。需要高度实时响应和低功耗的系统通常适合STM32&#xff0c;而需要网络连接和便捷无线通讯的物联网应用通常更适合ESP32。 一、架构与性能 STM32…

YOLOv11改进 | Neck篇 | 双向特征金字塔网络BiFPN助力YOLOv11有效涨点

YOLOv11改进 | Neck篇 | 双向特征金字塔网络BiFPN助力YOLOv11有效涨点 引言 目标检测领域的最新进展表明,特征金字塔网络(FPN)的设计对模型性能具有决定性影响。本文详细介绍如何将**双向特征金字塔网络(BiFPN)**集成到YOLOv11的Neck部分,通过改进的多尺度特征融合机制…

Python后端框架新星Robyn:性能与开发体验的双重革命

引言:Python后端框架的进化之路 在Web开发领域,Python生态长期被Flask、Django等经典框架主导。随着异步编程需求的增长和高并发场景的普及,开发者对框架性能提出了更高要求。2023年,一款名为Robyn的新型Web框架横空出世,以其独特的Rust底层架构和优雅的Python API设计,…

ADS学习笔记(三) 瞬态仿真

参考书籍:见资源绑定,书籍3.4 瞬态仿真,书籍链接: https://pan.baidu.com/s/1pjw8p7r3-1x8qcC1-hljFQ?pwd4v79 提取码: 4v79 本文为对实验内容的补充 瞬态仿真放大倍数与交流仿真不一致 为什么对同一个BJT电路进行交流信号仿真和进行瞬态仿真,得出交流信号仿真的放大倍数是3.…

Flask 会话管理:从原理到实战,深度解析 session 机制

1、Flask中session 的实现原理&#xff1a;服务器与客户端的协作 HTTP 协议是无状态的——服务器无法区分两次请求是否来自同一用户。这意味着&#xff0c;用户登录后跳转到其他页面时&#xff0c;服务器会“忘记”用户身份。 为解决这一问题&#xff0c;Web 开发中引入了会话…

学习STC51单片机16(芯片为STC89C52RCRC)

每日一言 那些让你喘不过气的日子&#xff0c;正是蜕变的开始。 串口编程寄存器分析&#xff08;红色框里面的这个是串口助手里面生成的波特率初始化函数哈&#xff09; 我们就根据以上的寄存器分析&#xff0c;因为这个是配置波特率的需要的寄存器 PCON smod 0 就是PCON的bit…

crud方法命名示例

以下是基于表名dste_project_indicator&#xff08;项目指标表&#xff09;的完整命名示例&#xff0c;覆盖各类增删改查场景&#xff1a; 1. 表名与实体类映射 // 表名&#xff1a;dste_project_indicator // 实体类&#xff1a;DsteProjectIndicatorEntity public class Ds…

AI时代新词-人工智能生成内容(AIGC)

一、什么是人工智能生成内容&#xff08;AIGC&#xff09;&#xff1f; 人工智能生成内容&#xff08;Artificial Intelligence Generated Content&#xff0c;简称AIGC&#xff09;是指利用人工智能技术生成的各种形式的内容&#xff0c;包括文字、图像、音频和视频等。AIGC的…