【动态规划】两个数组的 dp 问题一

news2024/9/20 12:31:28

两个数组的 dp 问题

  • 1.最长公共子序列
  • 2.不相交的线
  • 3.不同的子序列
  • 4.通配符匹配

在这里插入图片描述

点赞👍👍收藏🌟🌟关注💖💖
你的支持是对我最大的鼓励,我们一起努力吧!😃😃

1.最长公共子序列

题目链接:1143. 最长公共子序列

题目描述:

在这里插入图片描述

算法原理:

1.状态表示

这道题是遇到新的类型的题,两个字符串的dp问题。并且求的是最长公共子序列,我们很多题的经验就是从这道题来到。我们先总结一下这个经验。

经验 + 题目要求

  1. 选取第一个字符串 [0, i] 区间以及第二个字符串 [0, j] 区间作为研究对象
  2. 根据题目要求,确定状态表示

先以这两段区间来研究,这道题研究的是最长公共子序列。也就是说先看看上面s1这个区间所有子序列,以及下面s2这个区间所有子序列,其中的最长子序列。

dp[i][j] 表示:s1 的 [0, i] 区间以及 s2 的[0, j] 区间内所有子序列中,最长公共子序列的长度。

在这里插入图片描述
如果以某个位置为结尾所有子序列,还有在去找子序列,时间复杂O(N^4),而我们这里的子序列是所有的子序列,比如是说 [0,i] 区间所有的子序列。这个子序列既包含以i为结尾,也包含不以i为结尾的。

2.状态转移方程

根据最后一个位置的情况,分情况讨论

如果最后一个位置字符相等 s[i] == s[j]

是不是可以先在s1 [ 0 , i-1] 和 s2 [0 , j - 1]区间内所有子序列找最长公共子序列长度 然后 加上 1就行了

在这里插入图片描述

如果最后一个位置字符相等 s[i] != s[j]

那么最长公共子序列一定不是以 i j 位置为结尾,也就是说最长公共子序列一定不是同时选择 i 和 j 位置。那可以这样选

  1. s1[0, i - 1],s2[0, j] ,不选 i
  2. s1[0, i],s2[0, j -1] ,不选 j
  3. s1[0, i - 1],s2[0, j - 1], i j都不选

在这里插入图片描述
我们一般找状态转移方程要把所有情况不重不漏都找完,情况1 和 情况2 都包含情况3。但是注意我们的状态表示是最长公共子序列的长度,我们要找的是多种情况的max。所有这里的重复不会影响。但是如果要找的是公共子序列的个数,那就有问题了。遇到了再说。还有情况1和情况2包括情况3,所以填表的时候情况3就不用考虑了。

在这里插入图片描述

3.初始化

关于字符串的 dp 问题: 空串是有研究意义的

比如 s1 选一个空串,s2 选一个空串,其实也是一个公共子序列,不过公共子序列长度为0。

引入空串的概念之后,会方便我们的初始化

s1 [0,i] 区间,i最小0,最差就是 [0,0]是一定有一个字符的,如何表示空串呢?可以在原始 dp 表上多来一行一列,第一行表示第一个字符串为空,第二行表示第二个字符串为空。

并且填表也不会越界了。而且第一行表示第一个字符串为空,所以最长公共子序为0,第一列表示第二个字符串为空,所以最长公共子序也是0。

在这里插入图片描述

引入辅助节点要注意两个注意事项:

  1. 里面的值要保证我们后序填表是正确的
  2. 下标的映射关系

因为多开一行一列相当于dp表向右下移动一位,如果要回原表肯定是要 i-1,j-1。但是在字符串这里,我们可以在每个字符串前面加一个 ‘ ’,然后字符串有效字符就从1开始计数了。这个时候填表 1,1位置的时候就是对应字符串1,1位置。

4.填表顺序

填 i j 会遇到左上,上,左,因此从上往下填写每一行,每一行从左往右
在这里插入图片描述

5.返回值

dp[i][j] 表示:s1 的 [0, i] 区间以及 s2 的[0, j] 区间内所有子序列中,最长公共子序列的长度,而我们要的是s1,s2整个字符串的最长子序列的长度。因此 返回 dp[m][n]

class Solution {
public:
    int longestCommonSubsequence(string s1, string s2) {
        // 1.创建 dp 表
        // 2.初始化
        // 3.填表
        // 4.返回值

        int m = s1.size(), n = s2.size();
        s1 = ' ' + s1, s2 = ' ' + 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)// 每⼀⾏从左往右
                //if(s1[i - 1] == s2[j - 1])
                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.不相交的线

题目链接: 1035. 不相交的线

题目分析:

在这里插入图片描述

给两条独立的水平线按给定的顺序写下 nums1 和 nums2 中的整数。

可以以绘制一些连接两个数字 nums1[i] 和 nums2[j] 的直线,这些直线需要同时满足:

nums1[i] == nums2[j]
且绘制的直线不与任何其他连线(非水平线)相交。

请注意,连线即使在端点也不能相交:每个数字只能属于一条连线。

以这种方法绘制线条,并返回可以绘制的最大连线数。

在这里插入图片描述

先分析一下这道题,下面的最终结果,发现上面数组选252,下面数组也选252。这两个是一样的。是不是相当于在第一个数组中选一个子序列,然后在第二个数组里面也选一个子序列。然后选的这两个子序列要求必须得一样,且最长。那么正好是这两个数组里面得最长公共子序列。
在这里插入图片描述

由于这道题和上面几乎一模一样,一些细节就不在细说了

算法原理:

1.状态表示

经验 + 题目要求

  1. 选取第一个字符串 [0, i] 区间以及第二个字符串 [0, j] 区间作为研究对象
  2. 根据题目要求,确定状态表示

dp[i][j] 表示 :在n1里面的 [0,i] 区间以及 n2里面的 [0,j] 区间里面的所有子序列中,最长公共子序列的长度

在这里插入图片描述

2.状态转移方程

在这里插入图片描述

3.初始化

多开一行一列,填表就不会越界了。

  1. 里面的值要保证后序填表是正确的
  2. 下标的映射关系

第一行表示第一个数组里面是没有元素的。第一列表示第二个数组里面没有元素。因此给0。

整体往右下,填表要注意下标映射关系。
在这里插入图片描述

4,填表顺序

在这里插入图片描述

5.返回值

返回dp[m][n]

class Solution {
public:
    int maxUncrossedLines(vector<int>& nums1, vector<int>& nums2) {

        // 1.创建 dp 表
        // 2.初始化
        // 3.填表
        // 4.返回值

        int m = nums1.size(), n = nums2.size();
        vector<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][j - 1], dp[i - 1][j]);

        return dp[m][n];

    }
};

3.不同的子序列

题目链接:115. 不同的子序列

题目分析:

在这里插入图片描述

返回s 的 子序列 中 t 出现的个数

在这里插入图片描述

算法原理:

1.状态表示

经验 + 题目要求

  1. 选取第一个字符串 [0, i] 区间以及第二个字符串 [0, j] 区间作为研究对象
  2. 根据题目要求,确定状态表示

先不考虑整个字符串,先考虑 t [0, i] 区间,以及 s [0, j] 区间,先研究这两个小区间。我们题目要求在s的子序列里有多少个t,如果选这两个子区间的话相当于在 [0, j]这个区间里面的子序列里找一下有多少个 [0,i]子串。

在这里插入图片描述

所以我们的状态表示:

dp[i][j] 表示:s字符串 [0, j] 区间内所有子序列中,有多少个 t 字符串 [0, i] 区间内的子串

在这里插入图片描述

2.状态转移方程

我们是在s的 [0 ,j]子序列中找找有多少t [0,i] 子串。其实可以把 s 中 [0 ,j] 所有子序列划分成两类。

根据 s 的子序列的最后一个位置包不包含 s[j] 划分两类:

包含s[j]。子序列最后一个位置是j,我们要找子序列有多少t,那子序列要想有t,说明 t 最后一个位置和 j 是相等的。t[i] == s[j],要求dp[i][j],既然最后一个位置t[i] == s[j],可以先把它们去掉,然后在 t [0, i -1] 和 s [0 , j-1]研究问题,也就是说在 s [0 , j-1]里所有子序列中有多少个t [0, i -1] 子串。如果找到有多少个然后后面填上t[i] 和 s[j] 就行了。那有多少个呢?正好是 dp[i - 1][ j - 1],后面不加1。

在这里插入图片描述

不包含s[j],那就在 s [0, j -1] 所有子序列中找 t [0, i] 子串。

在这里插入图片描述

最后dp[i][j] 是这两种情况加起来

在这里插入图片描述

3.初始化

  1. 引入空串
  2. 里面的值要保证后序填表是正确的
  3. 下标的映射关系

引入空串就是在原始的dp中上面多加一行,左边多加一列。
第一行表示第一个字符串为空
第一列表示第二个字符串为空
这样填表就不会发生越界了

在这里插入图片描述

接下来看里面的值应该填多少

在字符串里,可以根据空串的性质初始化这些位置。

第一行表示 t 字符串为空串,t 如果是空串,s 不管是多长子序列里面都有一个空串。因此第一行应该全都是1

在这里插入图片描述

第一列表示 s 字符串为空串,只有当 t 是一个空串,才有一个子序列。 当 t 不是空串,怎么在 s 中也找不到一个子序列是 t。所有除了第一个位置外其他都是0。

在这里插入图片描述

下标映射关系。有两种方法

  1. 每个字符串前面加一个任意字符 s = ‘ ’ + s
  2. 整体往右下移动一步,回原本要-1

4.填表顺序

从上往下每一行,每一行从左往右

5.返回值

dp[i][j] 表示:s字符串 [0, j] 区间内所有子序列中,有多少个 t 字符串 [0, i] 区间内的子串。题目要求返回在 s 的 子序列 中 t 出现的个数。

在这里插入图片描述

class Solution {
public:
    int numDistinct(string s, string t) 
    {
        // 1.创建 dp 表
        // 2.初始化
        // 3.填表
        // 4.返回

        int m = t.size(), n = s.size();
        vector<vector<double>> dp(m + 1, vector<double>(n + 1));
        for(int j = 0; j <= n; ++j) dp[0][j] = 1;//初始化
        for(int i = 1; i <= m; ++i)
        {
            for(int j = 1; j <= n; ++j)
            {
                dp[i][j] += dp[i][j - 1];
                if(t[i - 1] == s[j - 1])
                    dp[i][j] += dp[i - 1][j - 1];
            }
        }
        return dp[m][n];
    }
};

4.通配符匹配

题目链接:44. 通配符匹配

题目描述:

在这里插入图片描述

算法原理:

1.状态表示

经验 + 题目要求

dp[i][j] 表示:p[0, j] 区间内的子串能否匹配 s[0, i] 区间内的子串

在这里插入图片描述

2.状态转移方程

根据最后一个位置的状况,分情况讨论

p[j]是普通字符,如果 i 位置 和 j 位置字符相等,并且 s [0, i - 1]能被 p[0, j -1] 匹配上,那 [0, i] 就能被 [0, j] 匹配上

在这里插入图片描述

p[j] == ‘?’ ,'?'可以匹配任何单个字符,因此只用去看s [0, i - 1]能否被 p[0, j -1] 匹配上

在这里插入图片描述

p[j] == ’ * ',( ’ * ’ 可以匹配任意字符序列(包括空字符序列)。)
如果p[j]匹配空串,我们就看dp[i][j-1]

在这里插入图片描述

如果p[j]匹配1个字符,我们就看dp[i-1][j-1]

在这里插入图片描述

如果p[j]匹配2个字符,我们就看dp[i-2][j-1]

在这里插入图片描述

dp[i][j] 时间复杂度就是O(N^2),在加上p[j] == ’ * ',时间复杂度就是O(N^3)了。想个办法看看p[j] == ’ * ',能否由若干个有限的状态表示。

优化:
第一种方法:数学

p[j] == ‘*’,有这么多种情况,只要有一种情况为真就可以了,所以我们可以得到下面的等式。发现 j -1是不变的,让等式所有 i 都减1。然后就可以进行替换了。

在这里插入图片描述

第二种方法:根据状态表示以及实际情况,优化状态转移方程

p[j] == ‘*’,找到dp[i][j]。

当j位置是 ‘*’,第一种情况还是去匹配空串。
在这里插入图片描述

第二种情况,去匹配一个字符 ,但是匹配完不把 j 位置丢掉,继续去看 s[0, i -1] 能否被 p[0, j]匹配

在这里插入图片描述
可能会有疑问,为什么’ * ‘就匹配一个,明明可以匹配多个,并且匹配完为什么不把’ * '丢掉。

我们这里是对 p[j] == ’ * ’ 好多情况的优化,我们让p[j] == ’ * ‘只匹配一个,但不舍去得到的 dp[i-1][j],也就是说在dp[i-1][j] 这一个状态里面,j位置 依旧是’ * ',它依旧有两种情况,要么去匹配空串,要么i-1匹配上,然后去dp[i-2][j]考虑匹配2个字符的情况。同理在dp[i-3][j]考虑匹配3个字符的情况。也就是说这个 j 会向下传递直到把s[0,i]所有字符匹配。

也就是说当匹配一个,不丢弃的话,进入dp[i-1][j],’ * ’ 继续会把 i -1匹配掉。同理进入dp[i-2][j],’ * ’ 继续会把 i -2匹配掉。。。所有仅需匹配一个不丢弃就可以把所有情况都考虑掉。

在这里插入图片描述

3.初始化

  1. 引入空串
  2. 里面的值要保证后序的填表是正确的
  3. 下标的映射关系

dp表上面多加一行表示s是空串,左边多加一列表示p是空串。接下来看看里面值应该填什么。

在这里插入图片描述
当 s 是空串,p也是空串,肯定能匹配上,所以第一行第一个格子是true

在这里插入图片描述
当s 是空串,但是 p 不是空串的话,注意 ’ * ‘ 可以匹配空串。现在就要对p的字符串就行讨论,然后初始化第一行后面的位置,如果s是空字符串,p字符串 前面出现’ * ‘ 可以匹配空串,但是 ’ * ‘ 之后出现普通字符了,后面不管有多少个’ * '都不能匹配了。
在这里插入图片描述
现在考虑第一列,如果p是空串,s也是空串,肯定能匹配,所以第一列第一个空格还是true。

后续如果 p 是空串,s不是空串,肯定匹配不上,所以第一列后续都是false

在这里插入图片描述

下标映射有两种方法:

  1. dp表多加一行一列整体向右下移动一位,如果要回原表需要 i -1,j -1 才行
  2. s = ’ ‘ + s,字符串前面加一个辅助字符,这样字符串字符就和dp表一一对应了。

4.填表顺序

从上往下填写每一行,每一行从左往右

在这里插入图片描述

5.返回值

dp[i][j] 表示:p[0, j] 区间内的子串能否匹配 s[0, i] 区间内的子串。题目要求是整个字符串,因此返回dp[m][n],m是s的长度,n是p的长度

class Solution {
public:
    bool isMatch(string s, string p) {
        // 1.创建 dp 表
        // 2.初始化
        // 3.填表
        // 4.返回值

        int m = s.size(), n = p.size();
        vector<vector<bool>> dp(m + 1, vector<bool>(n + 1));
        //初始化
        dp[0][0] = true;
        for(int j = 1; j <= n; ++j)
            if(p[j - 1] == '*') dp[0][j] = true;
            else break;
        
        for(int i = 1; i <= m; ++i)
            for(int j = 1; j <= n; ++j)
                if((s[i - 1] == p[j - 1] || p[j - 1] == '?') && dp[i - 1][j -  1])
                    dp[i][j] = true;
                else if(p[j - 1] == '*')
                    dp[i][j] = dp[i][j - 1] || dp[i - 1][j];

        return dp[m][n];
    }
};

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

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

相关文章

深度学习水印网络架构学习笔记

目前学习到的一些网络架构&#xff0c;简单整理如下。 1、END框架【嵌入器-噪声层-提取器】 HiDDeN: Hiding Data With Deep Networks. ECCV, 2018.END框架&#xff0c;对噪声层的设计。用可导操作模拟JPEG压缩的过程。 2、噪声层图像增强【Noise Layer】 MBRS: Enhancing R…

设计模式之外观设计模式

一、外观设计模式概念 外观模式 (Facade) 是一种结构型设计模式&#xff0c; 为子系统中的一组接口提供一个一致的界面&#xff0c;此模式定义了一个高层接口&#xff0c;这个接口使得这一子系统更加容易使用。 外观模式为复杂子系统提供了一个简单接口&#xff0c;并不为子系统…

[Python]案例驱动最佳入门:Python数据可视化在气候研究中的应用

在全球气候问题日益受到关注的今天&#xff0c;气温变化成为了科学家、政府、公众讨论的热门话题。然而&#xff0c;全球气温究竟是如何变化的&#xff1f;我们能通过数据洞察到哪些趋势&#xff1f;本文将通过真实模拟的气温数据&#xff0c;结合Python数据分析和可视化技术&a…

鸿蒙HarmonyOS开发:一次开发,多端部署(界面级)天气应用案例

文章目录 一、布局简介二、典型布局场景三、侧边栏 SideBarContainer1、子组件2、属性3、事件 四、案例 天气应用1、UX设计2、实现分析3、主页整体实现4、具体代码 五、运行效果 一、布局简介 布局可以分为自适应布局和响应式布局&#xff0c;二者的介绍如下表所示。 名称简介…

828华为云征文|华为云Flexus X实例docker部署最新Appsmith社区版,搭建自己的低代码平台

828华为云征文&#xff5c;华为云Flexus X实例docker部署最新Appsmith社区版&#xff0c;搭建自己的低代码平台 华为云最近正在举办828 B2B企业节&#xff0c;Flexus X实例的促销力度非常大&#xff0c;特别适合那些对算力性能有高要求的小伙伴。如果你有自建MySQL、Redis、Ng…

SQL优化-MySQL Explain中出现Select tables optimized away

文章目录 前言相关解释总结 前言 今天在做SQL优化的时候&#xff0c;在使用explain执行SQL时&#xff0c;出现了以下情况&#xff1a; EXPLAIN SELECT m1.id from station m1 INNER JOIN site s ON m1.codes.stationcode where receivetime(SELECT MAX(m2.receivetime) FROM…

基于Tesseract_OCR识别

1、安装Tesseract Mac版本&#xff0c;通过Homebrew进行安装即可brew install tesseract windows版本安装 下载地址&#xff1a;https://digi.bib.uni-mannheim.de/tesseract/ 2、更换语言包 下载语言包 https://github.com/tesseract-ocr/tesseract 亦可参照这个 Tessera…

【CTF Reverse】XCTF GFSJ1101 Mine- Writeup(反编译+动态调试+Base58编码)

Mine- 运气怎么这么差&#xff1f; 原理 Base58 Base58是用于比特币&#xff08;Bitcoin&#xff09;中使用的一种独特的编码方式&#xff0c;主要用于产生Bitcoin的钱包地址。 相比Base64&#xff0c;Base58不使用数字"0"&#xff0c;字母大写"O"&…

Linux 文件权限详解与管理

文章目录 前言一、文件权限概述1. 权限表示格式2. 权限组合值 二、查看文件权限三、修改文件所有者与所属组1. 使用 chown 修改文件所有者2. 使用 chgrp 修改文件所属组3. 添加所有者 四、修改文件权限1. 符号方式2. 八进制方式 总结 前言 在 Linux 系统中&#xff0c;文件权限…

React + Vite 多环境配置

1.根目录创建文件&#xff1a; .env.dev //测试环境 .env.development //本地环境 .env.production //正式环境 .env.uat //预发布环境 注&#xff1a;变量名必须使用 VITE_API 开头 2.package.json 配置&#xff1a; --mode 设置读取制定 .env文件 &#xff0c;默认读取.en…

Windows10安装cuda11.3.0+cudnn8.5.0,以及创建conda虚拟环境(pytorch)

1、检查电脑驱动版本为561.09&#xff0c;选择cuda版本&#xff0c;下图可知cuda版本<12.6。 nvidia-smi #查看驱动版本&#xff0c;以及最大可以安装的cuda版本 2、Anaconda3-2024.06-1-Windows-x86_64.exe下载&#xff1a; 官网&#xff1a;https://www.baidu.com/link?…

研究生存指南:必备Zotero插件,让你的文献管理更轻松

在读研阶段&#xff0c;我经常面临大量文献阅读和项目研究的任务。忽略文献整理会导致后续使用时非常不便&#xff0c;查找困难且混乱。导师向我们推荐了 Zotero&#xff0c;经过亲身试用&#xff0c;我发现它非常好用&#xff01;zotero有非常多的插件&#xff0c;能够一个就满…

了解Node开发基础知识

目录 定义架构应用场景安装版本工具代码执行REPL传递参数输出全局对象 定义 Node.js 是一个基于 V8 JavaScript 引擎构建的运行时环境&#xff0c;允许你在服务器端运行 JavaScript 代码。Node.js 允许开发者使用 JavaScript 编写服务器端代码&#xff0c;实现前后端代码的统一…

安全帽识别算法、安全帽智能识别、不戴安全帽检测算法

不戴安全帽检测算法是一种基于人工智能技术&#xff0c;用于实时监测和提醒工作人员是否正确佩戴安全帽的系统。以下是对不戴安全帽检测算法的详细介绍&#xff1a; 1. 技术原理 - 数据采集与预处理&#xff1a;通过安装在施工现场或工厂车间等场所的摄像头收集图像数据&#…

HTML 盒子标签、字符实体及废弃标签介绍

目录 HTML盒子标签 div标签 span标签 字符实体 HTML注释 HTML 废弃标签介绍 关注作者微信公众号&#xff0c;开启探索更多 HTML 知识的精彩之旅。在这里&#xff0c;你将收获丰富的 HTML 专业内容&#xff0c;深入了解这一网页开发语言的奥秘&#xff0c;不断拓展你的知识…

c语言面试字符串复制

1&#xff0c;下面这个函数的打印是什么&#xff1a; #include<stdio.h> #include<string.h>int main() {char str0[5], str1[] "welcome";strcpy(str0, str1);printf("str0:%s\r\n",str0);printf("str1:%s\r\n",str1); } larkla…

【Verilog学习日常】—牛客网刷题—Verilog快速入门—VL21

根据状态转移表实现时序电路 描述 某同步时序电路转换表如下&#xff0c;请使用D触发器和必要的逻辑门实现此同步时序电路&#xff0c;用Verilog语言描述。 电路的接口如下图所示。 输入描述&#xff1a; input A , input clk , …

uniapp+renderJS+google map开发安卓版APP非小程序

背景需求 需要在uniapp中接入google地图,研究了一番,都没有找到合适的,现在说一下教程。 效果图 前期工作 这两点缺一不可,否则你啥也看不到。 1、电脑安装L-O-U梯 用于访问G-OO-G-LE的API或者创建google map key。 2、手机安装L-O-U梯 用于显示google地图。我就是手…

SpringCloud从零开始简单搭建 - JDK17

文章目录 SpringCloud Nacos从零开始简单搭建 - JDK17一、创建父项目二、创建子项目三、集成Nacos四、集成nacos配置中心 SpringCloud Nacos从零开始简单搭建 - JDK17 环境要求&#xff1a;JDK17、Spring Boot3、maven。 那么&#xff0c;如何从零开始搭建一个 SpringCloud …

DNS攻击频发,打造防劫持DNS需强化“数据治理”理念

数字化转型时代&#xff0c;“一物多址&#xff0c;万物互联”正依托于DNS&#xff08;域名系统&#xff09;实现&#xff0c;DNS的重要性不言而喻。然而传统DNS协议存在诸多安全隐患&#xff0c;整个明文传输过程几乎没有认证与保护&#xff0c;导致DNS报文易被篡改&#xff0…