代码随想录56——动态规划:583两个字符串的删除操作、72编辑距离

news2025/7/14 15:31:53

文章目录

  • 1.583两个字符串的删除操作
    • 1.1.题目
    • 1.2.解答
  • 2.72编辑距离
    • 2.1.题目
    • 2.2.解答

1.583两个字符串的删除操作

参考:代码随想录,583两个字符串的删除操作;力扣题目链接

1.1.题目

在这里插入图片描述

1.2.解答

本题和 动态规划:115.不同的子序列 相比,其实就是两个字符串都可以删除了,情况虽说复杂一些,但整体思路是不变的。

这次是两个字符串可以相互删了,这种题目也知道用动态规划的思路来解,动规五部曲分析如下:

1.确定dp数组(dp table)以及下标的含义

dp[i][j]:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数
注意:这里说的是删除元素的最小次数,也就是删谁都可以。另外注意,这里以i-1/j-1结尾并不是说最后匹配的字符串中就必须有最后这两个字符,因为最后这两个字符可能是不相等的,那么就必须进行删除其中的一些字符让他们相等。

2.确定递推公式

(1)word1[i - 1]word2[j - 1]相同的时候,dp[i][j] = dp[i - 1][j - 1];。这个很好理解,因为此时两个字符串的最后一个字符是相等的, 所以在最后一个字符上他们谁都不需要删除字符。因此整个字符相等需要删除的个数就是除了最后一个字符之外的前面的字符串相等要删除的个数。

(2)word1[i - 1] word2[j - 1]不相同的时候,有三种情况:

  • 情况一:删word1[i - 1],最少操作次数为dp[i - 1][j] + 1。也就是说用word1当前字符之前的子串和word2做匹配,就相当于把word1的当前字符删掉了,所以最后结果要在子串匹配的结果基础上+1。
  • 情况二:删word2[j - 1],最少操作次数为dp[i][j - 1] + 1。这个原理同上,只不过删word2的字符。
  • 情况三:同时删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[i - 1][j - 1] + 1等于 dp[i - 1][j] 或 dp[i][j - 1],所以递推公式可简化为:dp[i][j] = min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);注意:这里就是说,dp[i - 1][j - 1] + 2可以分解成dp[i - 1][j] + 1 或 dp[i][j - 1] +1,虽然具体是哪个不知道,但是已经在前两种情况之中了,所以最后我们直接比较前两种情况中的最小值就行了,因为第三种情况的值就是前两种情况中的其中一个。

3.dp数组如何初始化

从递推公式中,可以看出来,dp[i][0]dp[0][j]是一定要初始化的。

  • dp[i][0]:word2为空字符串,以i-1为结尾的字符串word1要删除多少个元素,才能和word2相同呢,很明显dp[i][0] = i

  • dp[0][j]的话同理。

所以代码如下:

vector<vector<int>> dp(word1.size() + 1, vector<int>(word2.size() + 1));
for (int i = 0; i <= word1.size(); i++) dp[i][0] = i;
for (int j = 0; j <= word2.size(); j++) dp[0][j] = j;

4.确定遍历顺序

从递推公式 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]都是根据左上方、正上方、正左方推出来的。

所以遍历的时候一定是从上到下,从左到右,这样保证dp[i][j]可以根据之前计算出来的数值进行计算。

5.举例推导dp数组

word1:"sea",word2:"eat"为例,推导dp数组状态图如下:

在这里插入图片描述
最后给出代码如下:

int minDistance(string word1, string word2)
{
    // 1.定义dp数组
    vector<vector<int>> dp(word1.size()+1, vector<int>(word2.size()+1, 0));

    // 2.初始化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;
    
    // 3.动态规划:使用递推公式开始递推
    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]+2, min(dp[i-1][j]+1, dp[i][j-1]+1));
        }
    }

    // 最后返回结果
    return dp[word1.size()][word2.size()];
}

2.72编辑距离

参考:代码随想录,72编辑距离;力扣题目链接

2.1.题目

在这里插入图片描述

2.2.解答

注意:这道题目和上一道题目非常相似,简直可以说是一模一样。题目的关键在于如何理解在word1中增加一个字符,要把它等价转化为在word2中删除一个字符,具体如何理解看下面自己的解释。

编辑距离是用动规来解决的经典题目,这道题目看上去好像很复杂,但用动规可以很巧妙的算出最少编辑距离。接下来依然使用动规五部曲,对本题做一个详细的分析:

1.确定dp数组(dp table)以及下标的含义

dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]

2.确定递推公式

在确定递推公式的时候,首先要考虑清楚编辑的几种操作,整理如下:

if (word1[i - 1] == word2[j - 1])
    不操作
if (word1[i - 1] != word2[j - 1])
    增
    删
    换

(1) 当前字符相等,if (word1[i - 1] == word2[j - 1]), 那么说明不用任何编辑,dp[i][j] 就应该是 dp[i - 1][j - 1],即dp[i][j] = dp[i - 1][j - 1];

(2) 当前字符不相等,if (wor*d1[i - 1] != word2[j - 1]),此时就需要编辑了,需要对word1选择 其中一个操作:

  • 操作一:替换元素,把word1的当前元素替换成和word2的当前元素相等的,这里首先有一个替换操作,记录1次。然后此时剩下的编辑次数就是 以下标i-2为结尾的word1j-2为结尾的word2的最近编辑距离,也就是word1word2前面的子串进行匹配需要的最小编辑次数。因此使用替换元素的操作,最后操作次数为dp[i][j] = dp[i - 1][j - 1] + 1;

  • 操作二:word1删除一个元素,那么就是以下标i - 2为结尾的word1 j-1为结尾的word2的最近编辑距离 再加上一个操作。即 dp[i][j] = dp[i - 1][j] + 1;

  • 操作三:word1增加一个元素,此时等价于word2删除一个元素,那么就是以下标i - 1为结尾的word1 j-2为结尾的word2的最近编辑距离 再加上一个操作。即 dp[i][j] = dp[i][j - 1] + 1;
    注意:其实这里从word1中增加一个字符,等价于从word2中删除一个字符,理解如下:此时的大前提是word1word2的当前字符已经不相等了,所以我需要编辑word1的字符,让它和word2的当前字符相等。并且现在我们选择的是在word1的当前位置插入一个字符,自然我们插入的就是word2的当前字符,因为我们插入字符的目的就是让word1word2的最后一个字符相等。插入字符之后,相当于word2的最后一个字符已经确定和插入后新的word1的最后一个字符匹配了,那么我们要统计的修改次数就是 word2的前面的子串插入后新的word1的前面的子串(也就是原来未插入的word1 之间匹配的最小次数。
    比如原先word1 = "abcde"word2 = "abcfg",此时发现word2的最后一个字符gword1的最后一个字符e不匹配,所以我们就在word1的最后插入一个g,结果word1 = "abcdeg"word2 = abcfg。显然此时word1word2的最后一个字符g已经匹配上了,所以剩下的匹配要编辑的次数就是word1的前面的子串abcdeword2的前面的子串abcf之间进行匹配。可以发现,此时其实就是保持word1不动,而从word2中删除最后一个元素

综上,当 if (word1[i - 1] != word2[j - 1]) 时取最小的,即:dp[i][j] = min({dp[i - 1][j - 1], dp[i - 1][j], dp[i][j - 1]}) + 1;

递归公式代码如下:

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], dp[i - 1][j], dp[i][j - 1]}) + 1;
}

3.dp数组如何初始化

再次回顾dp数组的定义:dp[i][j] 表示以下标i-1为结尾的字符串word1,和以下标j-1为结尾的字符串word2,最近编辑距离为dp[i][j]

那么dp[i][0]dp[0][j] 表示什么呢?

  • dp[i][0] :以下标i-1为结尾的字符串word1,和空字符串word2,最近编辑距离为dp[i][0]。那么dp[i][0]就应该是i,对word1里的元素全部做删除操作,即:dp[i][0] = i;

  • 同理dp[0][j] = j;,就是对word1里面的元素全部做添加操作。

代码如下:

for (int i = 0; i <= word1.size(); i++) dp[i][0] = i;
for (int j = 0; j <= word2.size(); j++) dp[0][j] = j;

4.确定遍历顺序

从如下四个递推公式:

dp[i][j] = dp[i - 1][j - 1]
dp[i][j] = dp[i - 1][j - 1] + 1
dp[i][j] = dp[i][j - 1] + 1
dp[i][j] = dp[i - 1][j] + 1

可以看出dp[i][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], dp[i - 1][j], dp[i][j - 1]}) + 1;
        }
    }
}

5.举例推导dp数组

以示例1为例,输入:word1 = "horse", word2 = "ros"为例,dp矩阵状态图如下:
在这里插入图片描述

最后给出代码如下:

int minDistance(string word1, string word2)
{
    // 1.dp数组定义
    vector<vector<int>> dp(word1.size()+1, vector<int>(word2.size()+1, 0));

    // 2.dp数组初始化
    for(int i = 0; i <= word1.size(); i++)
        dp[i][0] = i;  // word2是空,只能是word1一直在删除
    for(int j = 0; j <= word2.size(); j++)
        dp[0][j] = j;  // word1是空,只能是word1一直在添加

    // 3.动态规划:使用递推公式开始递推
    for(int i = 1; i <= word1.size(); i++)
    {
        for(int j = 1; j <= word2.size(); j++)
        {
            // 1.word1字符等于word2字符,则不需要对word1当前位置做任何增删改操作
            if(word1[i-1] == word2[j-1])   
                dp[i][j] = dp[i-1][j-1];
                
            // 2.否则对word1有增删改三种情况,取他们操作的最小值:
            // (1)word1修改当前字符,让当前字符变成和word2的当前字符相等,那么此时匹配的最小修改次数
            //    就相当于word1/2都是用前面的子串进行匹配的次数,结果是dp[i-1][j-1]+1
            // (2)word1删除当前字符,即word1之前的子串匹配当前位置的word2,结果是dp[i-1][j]+1
            // (3)word1增加一个字符,其实这等效于把Word2删除当前字符,从而再与word1匹配,所以和(1)
            //    非常相似,只不过是删除word2中当前位置的字符,结果是dp[i][j-1]+1
            else
                dp[i][j] = min(dp[i-1][j-1], min(dp[i-1][j], dp[i][j-1])) + 1;
        }
    }

    // 最后返回结果
    return dp[word1.size()][word2.size()];
}

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

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

相关文章

在Python中 先乘再除 和 先除再乘 是有差别的

浮点数的原因<font colorblue size4 face"楷体">1 问题来源<font colorblue size4 face"楷体">2 为什么会这样&#xff1f;<font colorblue size4 face"楷体">2.1 分解公式<font colorblue size4 face"楷体">…

最近面试被问到的vue题

v-for 为什么要加 key 如果不使用 key&#xff0c;Vue 会使用一种最大限度减少动态元素并且尽可能的尝试就地修改/复用相同类型元素的算法。key 是为 Vue 中 vnode 的唯一标记&#xff0c;通过这个 key&#xff0c;我们的 diff 操作可以更准确、更快速 更准确&#xff1a;因为…

初阶牛之牛客网刷题集(1)

前言 记录一下牛牛自己在牛客网上刷到的一些题目.分享一下牛牛的解题思路,希望可以帮到大家. 目录前言1.母牛的故事解题思路&#xff1a;代码实现&#xff1a;2.替换空格解题思路:代码实现3.二进制中1的个数解题思路代码实现结语1.母牛的故事 题目链接:传送门 有一头母牛&am…

43期《深入浅出Pytorch》课程 - Task01:PyTorch的安装和基础知识+前置知识打卡

Task011、Pytorch安装2、基础知识2.1 张量(Tensor)2.2 自动求导2.3 梯度2.4 并行计算3、前置知识打卡1、Pytorch安装 由于之前使用过Pytorch&#xff0c;所以说不需要再重新下载&#xff0c;直接开始后续的基础知识 2、基础知识 由于之前学习过numpy系列&#xff0c;所以说…

用专业团队管理软件工具轻松“拿捏”年轻运营团队

本文旨在抛砖引玉&#xff0c;欢迎大家拍砖讨论&#xff0c;通过一款时下流行的专业团队管理软件飞项做案例&#xff0c;一起探讨和交流团队管理专业工具软件和一些对应的方法论。 说到国内这几年流行起来的团队管理工具软件&#xff0c;我们先看看互联网这几年的发展。这几年&…

网络舆情监测是干嘛的?

近年来&#xff0c;有赖于移动网络技术的发展和社会化媒体概念的崛起&#xff0c;讯息的传播与扩散速度得到了空前的提高&#xff0c;而对于民营企业来说同样带来了不少的挑战&#xff0c;接下来TOOM舆情监测系统小编带您简单介绍网络舆情监测是干什么的? 一、什么是网络舆情监…

用户画像系列——布隆过滤器在策略引擎中的应用

在用户画像系列——当我们聊用户画像&#xff0c;我们在聊什么&#xff1f; 介绍了用户画像的应用场景: (1)个性化推荐 通过用户标签给用户推荐合适的商品或者内容 (2)营销圈选 参考&#xff1a;用户画像系列——Lookalike在营销圈选扩量中的应用 (3)策略引擎 根据用户标…

HTML5网页设计制作基础大二dreamweaver作业、使用HTML+CSS技术制作博客网站(5个页面)

&#x1f389;精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

重磅推出!CrownCAD 2023全功能试用通道已开启!

CrownCAD 2023已于10月28日正式发布&#xff0c;为了更好地提升设计效率&#xff0c;不仅增强已有功能的智能性、标准性、应用性&#xff0c;更是在产品定义、属性、仿真、数据管理等方面再度突破&#xff0c;更加贴合中国制造需求。 CrownCAD 2023全功能试用通道已开启 为让…

如何在Win10上安装docker

[版权申明] 非商业目的注明出处可自由转载 出自&#xff1a;shusheng007 前言 没有了Mac Pro&#xff0c;我又捡起了我的Windows…&#xff0c;这是我给她安装docker的的记录&#xff0c;你愿意也可以瞅一眼 检查win10版本 我们的安装基于WSL2&#xff08;Windows subsystem…

【深入理解Kotlin协程】lifecycleScope源码追踪扒皮

lifecycleScope是LifecycleOwner的扩展属性&#xff0c;而 ComponentActivity 和 Fragment&#xff08;androidx&#xff09;都实现了 LifecycleOwner 接口&#xff0c;所以这就是为什么说lifecycleScope的作用范围是只能在Activity、Fragment中使用。public val LifecycleOwne…

vue-element-admin动态菜单(后台获取)

vue-element-admin动态菜单&#xff08;后台获取&#xff09;&#xff0c;此教程面向纯小白攻略&#xff0c;不要嫌我啰嗦&#xff0c;翻到自己需要的地方即可 前提 vue-element-admin官网&#xff1a; vue-element-admin (gitee.io) vue-element-admin页面展示&#xff1a;…

Spring Boot框架下实现Excel服务端导入导出

Spring Boot是由Pivotal团队提供的全新框架&#xff0c;其设计目的是用来简化新Spring应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置&#xff0c;从而使开发人员不再需要定义样板化的配置。今天我们就使用纯前对按表格控件带大家了解&#xff0c;如何在Spring…

高精度算法【加减乘除】

全文目录&#x1f60d; 前言&#x1f600; 高精度加法&#x1f914; 操作步骤&#x1f635;‍&#x1f4ab; 代码模板&#x1f600;高精度减法&#x1f914;操作步骤&#x1f635;‍&#x1f4ab; 代码模板&#x1f600;高精度乘法&#x1f914;操作步骤&#x1f635;‍&#x…

[R]第二节 对象介绍与赋值运算

前言 R 创建、控制的实体(entity)称为对象(object)。向量(vector)矩阵(matrix)数组(array)数据框(data frame)列表(list)因子(factor)函数(function)通过以上实体定义的更为一般性的结构(structures) 数据的存储形式 R语言进行数据存储选择一种合适的数据结构将已有的数据输入…

【Python实战】海量表情包炫酷来袭,快来pick斗图新姿势吧~(超好玩儿)

前言 有温度 有深度 有广度 就等你来关注哦~ 所有文章完整的素材源码都在&#x1f447;&#x1f447; 粉丝白嫖源码福利&#xff0c;请移步至CSDN社区或文末公众hao即可免费。 你有在聊天中遇到不知道该如何表达&#xff0c;如何回复的情况吗&#xff1f; 或许&#xff0c;使…

FastDFS学习(四)

目录&#xff1a; &#xff08;1&#xff09;FastDFS搭建集群的环境准备 &#xff08;2&#xff09;FastDFS集群搭建负载均衡环境-使用Nginx进行负载均衡 &#xff08;1&#xff09;FastDFS搭建集群的环境准备 架构图 如果你公司刚好用这个&#xff0c;那你就会搭建集群涉及…

python基于PHP+MySQL的个人博客系统毕设

随着时代和网络的发展,人们越来越希望通过多种模式来展示自己。于是个人博客就出现了,它可以更好的让人们来记录自己的工作和学习方式。博客不仅仅可以让自己抒发个人感情,还可以展示自己真实的生活,从而建立起一种友好的交友平台。 PHP个人博客系统毕设系统分为前台和后台两部…

第2-3-3章 文件处理策略-文件存储服务系统-nginx/fastDFS/minio/阿里云oss/七牛云oss

文章目录5.2 文件处理策略5.2.1 FileStrategy5.2.2 AbstractFileStrategy5.2.3 LocalServiceImpl5.2.4 FastDfsServiceImpl5.2.5 AliServiceImpl5.2.6 MinioServiceImpl5.2 文件处理策略 在开发fastDFS和minio实现类之前&#xff0c;需要提前安装部署好fastDFS和minio。搭建教程…

LIS.LCS.LCIS相关问题

文章目录P1020 [NOIP1999 普及组] 导弹拦截P1439 【模板】最长公共子序列P1637 三元上升子序列272. 最长公共上升子序列LCISP1020 [NOIP1999 普及组] 导弹拦截 P1020 [NOIP1999 普及组] 导弹拦截 导弹拦截应该是接触DP的第一题&#xff08;只不过洛谷上的数据加强了&#xff…