【动态规划篇】:动态规划中的“双线叙述”--如何用状态转移解决双序列难题

✨感谢您阅读本篇文章,文章内容是个人学习笔记的整理,如果哪里有误的话还请您指正噢✨
✨ 个人主页:余辉zmh–CSDN博客
✨ 文章所属专栏:动态规划篇–CSDN博客

在这里插入图片描述

文章目录

  • 一.双序列类DP
    • 解题步骤
  • 二.例题
    • 1.最长公共子序列
    • 2.不相交的线
    • 3.不同的子序列
    • 4.通配符匹配
    • 5.正则表达式
    • 6.交错字符串
    • 7.两个字符串的最小ASCLL码删除和
    • 8.最长重复子数组

一.双序列类DP

双序列通常是两个字符串或者两个数组,处理这类的DP,通常涉及到比较,匹配或转换操作。

解题步骤

  • 1.状态表示

    一般是二维数组,表示处理到两个序列的某个位置时的状态。

  • 2.状态转移方程

    从某段区间分析,根据当前字符是否相等以及可能的操作(如替换,插入,删除)来决定状态如何转换。

  • 3.初始化

    对于两个序列,都存在为空的情况,需要单独处理为空的状态值。

  • 4.填表顺序

    根据状态转移方程用到前状态的位置来决定填表顺序,按顺序填充状态表。

  • 5.返回值

    根据题意要求和状态表示确定返回值,可能是返回最后一个状态值也可能是找到状态表中的最大值等。

二.例题

1.最长公共子序列

题目

在这里插入图片描述

算法原理

本道题属于该类型的模板题,最经典的二维状态表表示双序列的状态,后面的题都是在此基础上进行变形。

对于双序列的分析,都是根据某段区间的情况,推出状态转移方程。

在这里插入图片描述

代码实现

int longestCommonSubsequence(string s1, string s2){int m = s1.size(), n = s2.size();//状态表示 dp[i][j]表示s1[0,i]区间和s2[0,j]区间的所有公共子序列中,最长的公共子序列长度//添加第0行和第0列,表示空字符串//初始化为0vector<vector<int>> dp(m + 1, vector<int>(n + 1));//使下标映射正确s1 = ' ' + s1;s2 = ' ' + s2;//填表for (int i = 1; i <= m; i++){for (int j = 1; j <= n; j++){//状态转移方程 分情况讨论if (s1[i] == s2[j]){dp[i][j] = dp[i - 1][j - 1] + 1;}else{dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);}}}//返回值return dp[m][n];
}

2.不相交的线

题目

在这里插入图片描述

在这里插入图片描述

算法原理

根据题意,将两个数组中相同的数字两线,但是不能存在相交的连线,要求找到最多的连线。转换一下其实就是找两个数组的最长公共子序列的长度,这样的连线一定不会存在相交。所以本道题和上面一道题可以说是完全相同,连状态表示和状态转移的推导都一样,只不过上一题是两个字符串,本道题是两个数组,但是处理方式还是一样的,这里就不再重复讲解,可以根据上一题的讲解来看。

代码实现

int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2){int m = nums1.size(), n = nums2.size();//状态表示 dp[i][j]表示nums1区间[0,i]和nums2区间[0,j]可以连线的个数//初始化 初始值设置为0vector<vector<int>> dp(m + 1, vector<int>(n + 1));//填表for (int i = 1; i <= m; i++){for (int j = 1; j <= n; j++){//状态转移方程 分情况讨论if (nums1[i - 1] == nums2[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 dp[m][n];
}

3.不同的子序列

题目

在这里插入图片描述

算法原理

本道题是判断其中一个字符串中有多少个子串和另一个字符串相等,还是从某段区间分析。

状态表示: dp[i][j]表示s区间[0,j]内的所有子序列中有多少个和t区间[0,i]的子串相等。

初始化: 添加第0行和第0列 第0行表示t字符串为空,则s字符串中一定存在一个子序列空串与t字符串相等,第0行初始值设置为1;第0列表示s字符串为空,则s字符串一定不存在子序列与t字符串相等,第0列初始值设置为0。

状态转移方程:根据当前字符s[j]包含还是不包含分情况讨论,如果不包含,找剩余区间中的相等个数(dp[i][j-1]);如果包含,需要先判断当前位置字符是否相等(s[j-1]==t[i-1]),然后找剩余区间中的相等个数(dp[i-1][j-1]);两种情况的个数相加就是当前位置的状态值。

填表顺序:从第一行到最后一行,其中每一行从左往右。

返回值:dp[n][m],其中m是s字符串的长度,n是t字符串的长度。

代码实现

const int num = 1000000007;
int numDistinct(string s, string t){int m = s.size(), n = t.size();//状态表示 dp[i][j]表示s区间[0,j]内的所有子序列中有多少个和t区间[0,i]的子串相等//初始化 添加第0行和第0列 第0行表示t字符串为空,则s字符串中一定存在一个子序列空串与t字符串相等,第0行初始值设置为1//第0列表示s字符串为空,则s字符串一定不存在子序列与t字符串相等,第0列初始值设置为0vector<vector<int>> dp(n + 1, vector<int>(m + 1));for (int i = 0; i <= m; i++){dp[0][i] = 1;}//填表for (int i = 1; i <= n; i++){for (int j = 1; j <= m; j++){//如果不包含当前s[j]字符dp[i][j] = dp[i][j - 1];//如果包含当前s[j]字符 需要先判断是否相等if (s[j - 1] == t[i - 1]){dp[i][j] += dp[i - 1][j - 1];}dp[i][j] %= num;}}//返回值return dp[n][m];
}

4.通配符匹配

题目

在这里插入图片描述

算法原理

本道题属于两个字符串的匹配问题,先从某段区间考虑,分析某段区间的匹配情况,从而推导出状态转移方程。因为算法原理较复杂,这里用画图讲解,如图所示:

在这里插入图片描述

代码实现

bool isMatch(string s, string p){int m = s.size(), n = p.size();//状态表示 dp[i][j]表示p[0,j]区间的子串能否匹配s[0,i]区间的子串vector<vector<bool>> dp(m + 1, vector<bool>(n + 1,false));//使下标映射正确s = " " + s;p = " " + p;//初始化dp[0][0] = true;for (int j = 1; j <= n; j++){if(p[j]=='*'){dp[0][j] = true;}else{break;}}//填表for (int i = 1; i <= m; i++){for (int j = 1; j <= n; j++){//状态转移方程 分情况讨论if (p[j] == '*'){dp[i][j] = dp[i - 1][j] || dp[i][j - 1];}else{dp[i][j] = (p[j] == '?' || p[j] == s[i]) && dp[i - 1][j - 1];}}}//返回值return dp[m][n];
}

5.正则表达式

题目

在这里插入图片描述

算法原理

本道题和上一题属于一类两个字符串的匹配问题,和上一题其实还是有点相似的,状态表示其实还是相同,根据某段区间的匹配情况来分析,只是状态转移方程中某些情况的分析不同,这里还是用画图讲解,如图所示:

在这里插入图片描述

代码实现

bool isMatch(string s, string p){int m = s.size(), n = p.size();//状态表示 dp[i][j]表示p[0,j]区间的子串能否匹配s[0,i]区间的子串//添加第0行和第0列表示两个字符串为空的情况vector<vector<bool>> dp(m + 1, vector<bool>(n + 1));//使下标映射正确s = " " + s;p = " " + p;//初始化dp[0][0] = true;for (int j = 2; j <= n; j += 2){if (p[j] == '*'){dp[0][j] = true;}//遇到非星号直接结束,剩余全为falseelse{break;}}//填表for (int i = 1; i <= m; i++){for (int j = 1; j <= n; j++){//状态转移方程 分情况讨论if (p[j] == '*'){dp[i][j] = dp[i][j - 2] || (p[j - 1] == '.' || p[j - 1] == s[i]) && dp[i - 1][j];}else{dp[i][j] = (p[j] == '.' || p[j] == s[i]) && dp[i - 1][j - 1];}}}//返回值return dp[m][n];
}

6.交错字符串

题目

在这里插入图片描述

在这里插入图片描述

算法原理

本道题虽然是三个字符串,但是第三个字符串是由前两个字符串拼接而成的,所以还是属于两个字符串类的DP问题,这里还是从某段区间考虑,分析前两个字符串的某段区间的子串能否拼接成第三个字符串的某段区间的子串。这里用画图讲解,如图所示:

在这里插入图片描述

代码实现

bool isInterleave(string s1, string s2, string s3){int m = s1.size(), n = s2.size();//预处理 下标0表示空串的情况s1 = " " + s1;s2 = " " + s2;//状态表示 dp[i][j]表示s1[1,i]区间的子串和s2[1,j]区间的子串能否拼成s3[1,i+j]区间的子串vector<vector<bool>> dp(m + 1, vector<bool>(n + 1,false));//初始化dp[0][0] = true;for (int i = 1; i <= m; i++){if (s1[i] == s3[i]){dp[i][0] == true;}else{break;}}for (int j = 1; j <= n; j++){if (s2[j] == s3[j]){dp[0][j] = true;}else{break;}}//填表for (int i = 1; i <= m; i++){for (int j = 1; j <= n; j++){//状态表示 根据s3最后一个字符分两种情况,满足其中一种即可//不能用两个if语句判断dp[i][j] = (s1[i] == s3[i + j] && dp[i - 1][j]) || (s2[j] == s3[i + j] && dp[i][j - 1]);}}//返回值return dp[m][n];
}

7.两个字符串的最小ASCLL码删除和

题目

在这里插入图片描述

算法原理

本道题第一眼可能没什么思路,但是反着来看就会比较简单,这里用到了正难则反的思想。如果反着来看就是找两个字符串的公共子序列最大ASCLL码和,然后用两个字符串总的ASCLL码和减去二倍的公共子序列中的最大ASCLL码和(这里减去二倍是因为存在两个字符串,需要减去两次公共子序列的最大ASCLL码和),因此本道题就转换成了找两个字符串的公共子序列的最大ASCLL码和。还是从某段区间来分析。

状态表示: dp[i][j]表示s1区间[0,i]和s2区间[0,j]的所有子序列中,最大ASCLL码和的公共子序列。

初始化:添加第一行,表示s1为空串的情况,添加第一列,表示s2为空串的情况,当其中一个为空串时,公共子序列也为空,最大ASCLL码和就为0,所以初始值全部设置为0。

状态转移方程:根据两个字符串最后一个位置的字符s1[i]s2[j]选还是不选分情况讨论,第一种情况:如果两个相等选一个即可,就找前一个位置的状态然后加上当前字符的ASCLL码;第二种情况:如果不相等,选择s1[i]字符,找剩余区间内的状态值(dp[i-1][j]);第三种情况:如果不相等,选择s2[j]字符,找剩余区间内的状态值(dp[i][j-1]);第四种情况:如果不相等,两个都不选,找剩余区间内的状态值(dp[i-1][j-1]),但是这种情况在第二种和第三种情况中都已经包括该区间,所以可以省去。最后取三种情况中的最大值。

填表顺序:从第一行到最后一行,其中每一行从左往右。

返回值:两个字符串总的ASCLL码和减去二倍的公共子序列中的最大ASCLL码和(sum-2*dp[m][n])。

代码实现

int minimumDeleteSum(string s1, string s2){//正难则反思想 找两个字符串的公共子序列最大ASCLL码和int m = s1.size(), n = s2.size();//状态表示 dp[i][j]表示s1区间[0,i]和s2区间[0,j]的所有子序列中,最大ASCLL码和的公共子序列//添加第一行,表示s1为空串的情况,添加第一列,表示s2为空串的情况vector<vector<int>> dp(m + 1, vector<int>(n + 1));//填表for (int i = 1; i <= m; i++){for (int j = 1; j <= n; j++){//状态转移方程dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);if (s1[i - 1] == s2[j - 1]){dp[i][j] = max(dp[i - 1][j - 1] + s1[i-1], dp[i][j]);}}}//统计两个字符串总的ASCLL码和int sum = 0;for(auto ch : s1){sum += ch;}for(auto ch : s2){sum += ch;}//返回值,总的和减去两个最大和公共子序列就是需要最小删除的和return sum - 2 * dp[m][n];
}

8.最长重复子数组

题目

在这里插入图片描述

算法原理

本道题第一眼看可能会觉得和最长公共子序列差不多,都是要找两个数组或字符公共的,但是这里有一个不同点,对于最长公共子序列那道题要找的是子序列,而本道题要找到是子数组,这两个的区别就是,子数组以nums[i]为结尾时,前一个必须是nums[i-1],相邻值才是子数组;而子序列以nums[i]为结尾时,前一个不一定是nums[i-1],可以是nums[i-2]也可以是nums[i-3]等等许多情况。因此本道题的状态表示就不能和前面的几道题一样,从某段区间中的子序列分析,而是必须以当前位置元素为结尾来分析。

状态表示 dp[i][j]表示nums1以i位置元素为结尾和nums2以j位置元素为结尾的公共子数组中的,最长的公共子数组长度

初始化 添加第0行和第0列,表示两个数组为空数组的情况,初始值设置为0

状态转移方程:根据两个数组当前位置元素来分析,如果nums[i-1]==nums[j-1](因为添加了第0行和第0列,映射到原数组中下标要减一),只需找到以前一个位置元素为结尾时的最长公共子数组长度(dp[i-1][j-1])然后加一即可。如果nums[i-1]!=nums[j-1],因为状态表示是以当前位置元素为结尾的公共子数组,既然结尾的两个元素都不想等,所以就不存在公共子数组,长度就为0。

填表顺序:从第一行到最后一行,其中每一行从左往后。

返回值:因为不知道最长的公共子数组是以哪个位置元素为结尾的,所以需要遍历整个状态表,找到最大值返回。

代码实现

int findLength(vector<int>& nums1, vector<int>& nums2){int m = nums1.size(), n = nums2.size();//状态表示 dp[i][j]表示nums1以i位置为结尾和nums2以j位置为结尾的公共子数组中的,最长的公共子数组长度//初始化 添加第0行和第0列,表示两个数组为空数组的情况,初始值设置为0vector<vector<int>> dp(m + 1, vector<int>(n + 1));//返回值 因为不确定最长的公共子数组以那两个位置为结尾,所以需要找到状态表中的最大值int ret = 0;//填表for (int i = 1; i <= m; i++){for (int j = 1; j <= n; j++){if (nums1[i - 1] == nums2[j - 1]){dp[i][j] = dp[i - 1][j - 1] + 1;}ret = max(ret, dp[i][j]);}}return ret;
}

以上就是关于双序列类DP例题的讲解,如果哪里有错的话,可以在评论区指正,也欢迎大家一起讨论学习,如果对你的学习有帮助的话,点点赞关注支持一下吧!!!
在这里插入图片描述

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

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

相关文章

观察者模式说明(C语言版本)

观察者模式主要是为了实现一种一对多的依赖关系&#xff0c;让多个观察者对象同时监听某一个主题对象。这个主题对象在状态发生变化时&#xff0c;会通知所有观察者对象&#xff0c;使它们能够自动更新自己。下面使用C语言实现了一个具体的应用示例&#xff0c;有需要的可以参考…

yolo11s rknn无法detect的bugfix - step by step

1.缘起 上周四下班时&#xff0c;发现在宿主机环境工作良好的既有的pytorch模型&#xff0c;在通过.pt->.onnx->.rknn的转换后无法正常工作。周五下班时&#xff0c;怀疑疑点在两处&#xff1a; 版本匹配问题通道和参数传递问题。 周六&#xff0c;周日&#xff0c;周…

前端JS接口加密攻防实操

前端JS接口加密攻防实操 背景 在爬虫过程中&#xff0c;对数据接口各类加密的经历总结&#xff0c;无头消耗资源效率不高&#xff0c;采用浏览器兜底解密协程并行 青铜版(混淆对称加密|签名nonce等&#xff09; 解&#xff1a;根据API 调用栈&#xff0c;断点找到request参…

15.3 多线程3

版权声明&#xff1a;本文为博主原创文章&#xff0c;转载请在显著位置标明本文出处以及作者网名&#xff0c;未经作者允许不得用于商业目的。 15.3.6 线程返回值 如果需要从线程的方法中获得计算的值&#xff0c;可以考虑使用模块级公共变量&#xff0c;在线程对应的方法中最…

同步异步日志系统-日志落地模块的实现

功能&#xff1a;将格式化完成后的日志消息字符串&#xff0c;输出到指定的位置 扩展&#xff1a;支持同时将日志落地到不同的位置 位置分类&#xff1a; 1.标准输出 2.指定文件&#xff08;时候进行日志分析&#xff09; 3.滚动文件&#xff08;文件按照时间/大小进行滚动…

【Kubernetes】k8s 部署指南

1. k8s 入门 1.1 k8s 简介 需要最需要明确的就是&#xff1a;kubernetes&#xff08;简称 k8s &#xff09; 是一个 容器编排平台 &#xff0c;换句话说就是用来管理容器的&#xff0c;相信学过 Docker 的小伙伴对于容器这个概念并不陌生&#xff0c;打个比方&#xff1a;容器…

Spring AI接入DeepSeek:快速打造微应用

随着DeepSeek-R1的官宣开源&#xff0c;DeepSeek迅速成为AI领域的热门话题&#xff0c;吸引了大量开发者和研究者的关注。这一开源举措不仅推动了技术的普及&#xff0c;也促使更多企业和机构加入到开源生态中。例如&#xff0c;国内大厂X度于2月14日宣布将在未来几个月推出文新…

MyBatis映射文件常用元素详解与示例

引言 MyBatis是一个优秀的持久层框架&#xff0c;它支持定制化SQL、存储过程以及高级映射。MyBatis的配置文件和映射文件分离&#xff0c;映射文件用于定义SQL语句和结果映射。本文将介绍MyBatis映射文件中常用的元素及其示例用法。 一、基础CRUD元素 1. <mapper> 作用…

SqlDbx 是一款数据库管理工具资源分享

SqlDbx_Chs 是 SqlDbx 的中文版本。SqlDbx 是一款数据库管理工具&#xff0c;支持多种数据库系统&#xff0c;如 MySQL、Oracle、SQL Server、PostgreSQL 等&#xff0c;主要用于执行 SQL 查询、管理数据库对象及数据操作。 主要功能 SQL 查询执行&#xff1a;支持编写和运行…

Dav_笔记14:优化程序提示 HINTs -4

指定全局表提示 指定表的提示通常是指发生提示的DELETE&#xff0c;SELECT或UPDATE查询块中的表&#xff0c;而不是指语句引用的任何视图中的表。 如果要为显示在视图中的表指定提示&#xff0c;Oracle建议使用全局提示&#xff0c;而不是在视图中嵌入提示。 您可以使用包含具…

Python 文本探秘:正则表达式的易错迷宫穿越 -- 7. 正则表达式

正则表达式是 Python 中处理文本的强大武器&#xff0c;但它复杂的语法和规则构成了一个易错迷宫。本文深入剖析了正则表达式模式编写的错误、匹配规则的误解、性能优化的忽视等问题。通过大量的文本处理实例&#xff0c;展示了错误的正则表达式使用方式以及正确的解决方案。帮…

PPT工具集

PPT模版 免费下载 爱PPT优品PPTPPT之家第一PPTOfficePlus部分免费 AI生成PPT Kimi秘塔搜索 可以输入内容生成PPT大纲。

rocketmq-netty通信设计-request和response

1、NettyRemotingServer启动分析 org.apache.rocketmq.remoting.netty.NettyRemotingServer#start public void start() {this.defaultEventExecutorGroup new DefaultEventExecutorGroup(nettyServerConfig.getServerWorkerThreads(),new ThreadFactory() {private AtomicI…

Datawhale 数学建模导论二 笔记1

第6章 数据处理与拟合模型 本章主要涉及到的知识点有&#xff1a; 数据与大数据Python数据预处理常见的统计分析模型随机过程与随机模拟数据可视化 本章内容涉及到基础的概率论与数理统计理论&#xff0c;如果对这部分内容不熟悉&#xff0c;可以参考相关概率论与数理统计的…

git如何下载指定版本

要使用Git下载指定版本&#xff0c;可以通过以下步骤进行操作‌&#xff1a; ‌1. 使用Git命令行下载指定版本‌&#xff1a; 1.1 首先&#xff0c;使用git clone命令克隆整个git库到本地。例如&#xff1a;git clone [库的URL]。这将下载最新的代码到本地。‌ 1.2 进入克隆…

安卓基础(持续更新的笔记)

为什么要这样&#xff1a; // 创建请求体RequestBody body RequestBody.create(MediaType.parse("application/json; charsetutf-8"),jsonObject.toString()); jsonObject 就包含了一个 JSON 数据&#xff0c;它其实就是&#xff1a; {"name": "张…

Spring Boot应用开发

一、了解Spring Boot Spring Boot是一个基于Spring框架的开源Java基础框架&#xff0c;它可以帮助我们快速开发独立的、基于生产级的基于Spring框架的应用程序。简单来说&#xff0c;它就像是一个“超级助手”&#xff0c;帮你把很多复杂的配置都简化了&#xff0c;让你能更轻…

算法笔记——字典树

什么是字典树&#xff1f; 一棵字典树树就像一个小型字典一样&#xff0c;当你拿到一个字想去字典上查的时候&#xff08;以拼音查法为例&#xff09;&#xff0c;你会先查这个拼音的开头字母&#xff0c;然后在按需查找他下一个字母直到找到相对应的拼音才可以。字典树也是如此…

Python实现AWS Fargate自动化部署系统

一、背景介绍 在现代云原生应用开发中,自动化部署是提高开发效率和保证部署质量的关键。AWS Fargate作为一项无服务器计算引擎,可以让我们专注于应用程序开发而无需管理底层基础设施。本文将详细介绍如何使用Python实现AWS Fargate的完整自动化部署流程。 © ivwdcwso (ID…

单元测试整理

在国外软件开发中&#xff0c;单元测试必不可少&#xff0c;但是国内并不太重视这一块&#xff0c;一个好的单元测试可以提前发现很多问题&#xff0c;也减去和测试battle的时间 Spring单元测试 JUnit4 RunWith 指明单元测试框架 e.g. RunWith(SpringJUnit4ClassRunner.cla…