当前位置: 首页 > news >正文

两个数组的dp问题

文章目录

  • 正则表达式匹配
  • 交错字符串
  • 两个字符串的最小ASCII删除和
  • 最长重复子数组
  • 最长公共子序列
  • 不相交的线
  • 不同的子序列
  • 通配符匹配
  • 总结

正则表达式匹配

在这里插入图片描述
一、题目解析
给定一个字符串s和一个正则表达式p,实现一个支持’.‘和’*'的正则表达式匹配。其中:

  • '.'可以匹配任意单个字符。
  • '*'表示前面的元素可以出现任意次(包括 0 次)。

二、算法原理

  1. 状态表示
    用dp[i][j]表示:p中[0, j]区间内的子串是否能够匹配s中[0, i]区间内的子串,值为true或false。

  2. 状态转移方程
    根据p最后一个位置的状态,分情况讨论:

  • 若p[j]是普通字符(非’.‘和’*'):则需p[j] == s[i]且dp[i - 1][j - 1] == true,此时dp[i][j] = true。

  • 若p[j] == ‘.’:则dp[i][j] = dp[i - 1][j - 1]。

  • 若p[j] == ‘*’:

    • 当p[j - 1]重复 0 次时,dp[i][j] = dp[i][j - 2]。

    • 当p[j - 1]重复至少 1 次时,需p[j - 1]能匹配s[i](p[j - 1] == s[i]或p[j - 1] == ‘.’)且dp[i - 1][j] == true,此时dp[i][j] = dp[i - 1][j]。

p[j]='*‘时,通常是分类p[j-1]为普通字符 还是 ‘.’ ,递推过程写出后可以总结为p[j-1]重复次数。

  1. 初始化
  • 引入空串,确保后续填表逻辑正确。
  • 处理边界情况,如下标转移等,例如让s = " " + s,p = " " + p。
    (0,0)两空串能匹配
    (i,0) p为空,s不为空时,全false
    (0,j)s为空,当p为空时(当偶数位全是‘*’时,p为空),true
  1. 填表顺序
  • 从上往下填写每一行。
  • 每一行从左往右填写。
    根据状态转移方程,dp[i][j]由dp[i-a]j-b推导而来,也就是根据左上角的状态推导,故而上–》下,左–》右。
  1. 返回值
    返回dp[m][n](m为s的长度,n为p的长度)。

写状态转移方程时,都是根据p最后位置的状态分类讨论的,可以直接边讨论边写,最后再把情况相似的优化。

我这里的m n 是原串长度+1,所以最后返回(m-1,n-1)

class Solution {
public:bool isMatch(string s, string p) {//引入空串,防止越界s=" "+s;p=" "+p;int m=s.size(),n=p.size();vector<vector<bool>>dp(m,vector<bool>(n,false));//初始化dp[0][0]=true;for(int j=2;j<n;j+=2)//s为空串{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]==s[i]||p[j]=='.')dp[i][j]=dp[i-1][j-1];else if(p[j]=='*')//最后为*{if(p[j-1]=='.'){//dp[i][j]=dp[i][j-2]||dp[i-1][j-2]||dp[i-2][j-2]||dp[i-3][j-2].....//同理可得-> dp[i-1][j]=dp[i-1][j-2]||dp[i-2][j-2]||... 所以dp[i][j]=dp[i][j-2]||dp[i-1][j];}else{//p[j-1]是普通字符: 和*组合起来,可以匹配0个/n个dp[i][j]=dp[i][j-2]||(p[j-1]==s[i]&&dp[i-1][j]);}}}return dp[m-1][n-1];}
};

交错字符串

在这里插入图片描述
在这里插入图片描述
一、题目解析
给定三个字符串 s1、s2、s3,需判断 s3 是否能由 s1 和 s2 交替拼接而成。
二、算法原理(动态规划)

  1. 预处理
    为了方便后续处理边界情况,对三个字符串进行预处理,给每个字符串前面添加一个空字符,即 s1 = " " + s1,s2 = " " + s2,s3 = " " + s3。
  2. 状态表示
    用 dp[i][j] 表示:s1 中 [1, i] 区间内的字符以及 s2 中 [1, j] 区间内的字符,能否拼接成 s3 中 [1, i + j] 区间内的字符。
  3. 状态转移方程
    根据拼接的最后一个位置的字符来源,分情况讨论:
  • 若最后一个字符来自 s1,即 s1[i] == s3[i + j],且 dp[i - 1][j] == true,那么 dp[i][j] = true。
  • 若最后一个字符来自 s2,即 s2[j] == s3[i + j],且 dp[i][j - 1] == true,那么 dp[i][j] = true。

状态转移方程推到的时候,不要并列顺序写这两种情况,因为它们只要有一个是true,那结果就是true。是或(||)的关系。
if(s1[i]==s3[i+j])dp[i][j]=dp[i-1][j];
if(s2[j]==s3[i+j])dp[i][j]=dp[i][j-1];(X )

dp[i][j]=(s1[i]==s3[i+j]&&dp[i-1][j])||
(s2[j]==s3[i+j]&&dp[i][j-1]);

  1. 初始化
    构建一个二维表格来存储状态,初始时表格中的值需要根据实际情况进行合理初始化,以保证后续状态转移的正确性。
  2. 填表顺序
  • 从上往下填写每一行。
  • 每一行从左往右填写。
  1. 返回值
    最终的结果由 dp[m][n] 决定,其中 m 是 s1 的长度,n 是 s2 的长度,它表示 s1 全部字符和 s2 全部字符能否拼接成 s3 全部字符。
class Solution {
public:bool isInterleave(string s1, string s2, string s3) {int m=s1.size(),n=s2.size();if(m+n!=s3.size())return false;//加空串方便填表s1=" "+s1,s2=" "+s2,s3=" "+s3;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++){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];}
};

两个字符串的最小ASCII删除和

在这里插入图片描述
在这里插入图片描述
题目解析

  • 问题描述:给定两个字符串 s1 和 s2,返回使两个字符串相等所需删除字符的 ASCII 值的最小和。

  • 分析:求两个字符串里面所有的公共子序列里面,ASCII 值的最大和。最后再用整体的ASCII值-最大*2
    算法原理
    1.状态表示

  • dp[i][j] 表示:s1 的 0…i 区间以及 s2 的 0…j 区间内的所有子序列里,公共子序列的 ASCII 最大和。

2.状态转移方程

  • 根据最后一个位置的状况,分情况讨论:
  • 有 s1[i],有 s2[j] → s1[i] == s2[j] → dp[i][j] = dp[i-1][j-1] + s1[i]
  • 有 s1[i],有 s2[j] → s1[i] != s2[j] → dp[i][j] = max(dp[i-1][j], dp[i][j-1])
  • 没有 s1[i],有 s2[j] → dp[i][j] = dp[i-1][j]
  • 没有 s1[i],没有 s2[j] → dp[i][j] = dp[i][j-1]
    3,4的情况包含到2中了

3.初始化

  • 下标的映射关系 → i 减 1

4.填表顺序

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

5.返回值

  • dp[m][n]
  • 统计 2 个字符串的 ASCII 和 → sum
  • sum - dp[m][n] * 2
class Solution {
public:int minimumDeleteSum(string s1, string s2) {int m=s1.size(),n=s2.size();vector<vector<int>>dp(m+1,vector<int>(n+1,0));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][j],dp[i-1][j-1]+s1[i-1]);}int sum1=0,sum2=0;for(auto e:s1)sum1+=e;for(auto e:s2)sum2+=e;return sum1+sum2-2*dp[m][n];}
};

最长重复子数组

在这里插入图片描述
一、题目解析
给定两个整数数组 nums1 和 nums2,需找出它们的最长连续公共子数组的长度。

  • 示例:nums1 = [1,2,3,2,1],nums2 = [3,2,1,4,7],最长重复子数组为 [3,2,1],长度为 3。

二、算法原理

  1. 状态表示
    dp[i][j]:以 nums1 第 i 个元素、nums2 第 j 个元素结尾的最长重复子数组长度。

这里无法找区间内的最长重复子数组,只能是以i/j为结尾找。因为子数组要是连续的,就算最后的位置相同,前面不连续结果也是0。子序列不要求连续就可以通过范围定状态表示。

  1. 状态转移方程
  • 若 nums1[i-1] != nums2[j-1]:无法形成连续重复子数组,dp[i][j] = 0。
  • 若 nums1[i-1] == nums2[j-1]:可延长之前的重复子数组,dp[i][j] = dp[i-1][j-1] + 1。
  1. 初始化
    创建 (m+1)×(n+1) 的 dp 数组(m、n 为数组长度),所有元素初始化为 0。

  2. 填表顺序
    从上到下、从左到右遍历填充 dp 数组。

  3. 返回值
    遍历 dp 数组,返回其中的最大值(所有可能的最长重复子数组长度)。

class Solution {
public:int findLength(vector<int>& nums1, vector<int>& nums2) {int m=nums1.size(),n=nums2.size();//以 nums1 第 i 个元素、nums2 第 j 个元素结尾的最长重复子数组长度。vector<vector<int>>dp(m+1,vector<int>(n+1,0));int res=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;else dp[i][j]=0;res=max(res,dp[i][j]);}return res;}
};

最长公共子序列

在这里插入图片描述
在这里插入图片描述
状态表示:
dp[i][j]:s1的(0,i)区间,与s2的(0,j)区间中,最长公共子序列的长度。

子序列不要求连续,直接按区间讨论就行

class Solution {
public:int longestCommonSubsequence(string s1, string s2) {s1=" "+s1;s2=" "+s2;int m=s1.size(),n=s2.size();vector<vector<int>>dp(m,vector<int>(n,0));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-1][n-1];}
};

不相交的线

在这里插入图片描述
在这里插入图片描述
这题看题意其实和上一题找最长公共子序列是一样的。

class Solution {
public:int maxUncrossedLines(vector<int>& n1, vector<int>& n2) {int m=n1.size(),n=n2.size();vector<vector<int>>dp(m+1,vector(n+1,0));for(int i=0;i<m;i++)for(int j=0;j<n;j++){if(n1[i]==n2[j])dp[i+1][j+1]=dp[i][j]+1;else{dp[i+1][j+1]=max(dp[i][j+1],dp[i+1][j]);}}return dp[m][n];}
};

不同的子序列

在这里插入图片描述
状态表示:
dp[i][j]:s的(0,i)区间的所有子序列中有多少t的(0,j)区间内的子串

状态转移方程:
一样从最后的元素讨论:
s[i-1] == t[j-1]时,

  • dp[i-1][j-1]:选 s[i-1] 去匹配 t[j-1],此时需统计「s 前 i-1 个字符匹配 t 前 j-1 个字符」的组合数。
  • dp[i-1][j]:不选 s[i-1],此时需统计「s 前 i-1 个字符匹配 t 前 j 个字符」的组合数。

不等时,直接不考虑s的最后字符,再匹配dp[i][j] = dp[i-1][j]。

方便填表引入空串,所有s t的下标都-1

下面代码的i j和上面思路的i j的顺序是相反的

class Solution {
public:int numDistinct(string s, string t) {s=" "+s;t=" "+t;int m=t.size(),n=s.size();vector<vector<double>>dp(m,vector<double>(n,0));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++){
if(s[j]==t[i])dp[i][j]=dp[i-1][j-1]+dp[i][j-1];
else
{dp[i][j]=dp[i][j-1];
}}return dp[m-1][n-1];}
};

通配符匹配

在这里插入图片描述
在这里插入图片描述
状态表示:
dp[i][j]:s的(0,i) 和p的(0,j)区间能匹配。

思路一样是分类讨论p的最后一位,是* ? 或者普通字符。
如果是* 的话可以匹配1/2/3…位字符,
就是dp[i][j] = dp[i-1][j-1] || dp[i-2][j-2] || dp[i-3][j-3] || …
后面可以直接等量代换,dp[i-1][j]= dp[i-2][j-1] || …
代换完就是 dp[i-1][j-1] || dp[i-1][j]
其他两类分析比较简单。

再根据分析的动态转移方程,我们知道dp[i][j] 都是由 dp[i-a][j-b] (a,b>0)推导来的,也就是 二维表的 左上角 推到到–》 右下角, 所以填表顺序(遍历顺序)就是 左–》右 && 上–》下

class Solution {
public:bool isMatch(string s, string p) {s=" "+s;p=" "+p;int m=s.size(),n=p.size();vector<vector<bool>>dp(m,vector<bool>(n,false));dp[0][0]=true;int flag=1;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{if(p[j]==s[i]|| p[j]=='?')dp[i][j]=dp[i-1][j-1];}}return dp[m-1][n-1];}
};

总结

1.状态表示:

  • 明确 dp[i][j] 的含义(聚焦 “数组 / 字符串 A 的前i个元素 + 数组 / 字符串 B 的前j个元素” 的属性,如 “能否匹配”“最长长度”“方案数” 等)。
  • 「子数组(连续)」:常定义为 “以 A [i]、B [j] 结尾” 的状态(利用 “连续性” 约束转移,如「最长重复子数组」)。
  • 「子序列(不连续)」:常定义为 “A [0…i]、B [0…j] 区间内” 的状态(无需连续,只需整体最优,如「最长公共子序列」「不同子序列数」)。

2.状态转移:

  • 核心逻辑:从 “最后一个元素的关系” 入手分类讨论(如字符是否相等、是否为特殊字符*/.等)。
  • 转移方向:根据依赖关系,可能是 “左上角推导”(如dp[i-1][j-1])、“左侧 / 上侧推导”(如dp[i][j-1]/dp[i-1][j]),或 “多情况合并”(如*匹配 0 次 / 多次的或逻辑)。

3.初始化:

  • 技巧:给原字符串 / 数组前添加 “空串 / 空元素”,将 “空” 的边界情况统一到状态中(避免单独处理i=0或j=0的特殊逻辑)。
http://www.dtcms.com/a/483711.html

相关文章:

  • 有没有免费的网站服务器网站开发离线下载报表
  • 网站服务器ip地址怎么查世界500强企业排名
  • 万网租空间 网站网站优化改版
  • 网站推广公司渠道WordPress入门编辑器
  • 大连城市建设档案馆官方网站php 网站反盗链
  • 解锁 Python 多线程新纪元:深入体验 3.14 的 Free-Threading 功能
  • 【框架演进】Vue与React的跨越性变革:从Vue2到Vue3,从Class到Hooks
  • ASP.NET Core Blazor简介和快速入门(基础篇)
  • 找印度人做网站网站建设经费预算
  • 孝感网站建设公司学院 网站 两学一做
  • 网站建设费用5万入账企业注册号查询系统
  • Redis概述
  • 南京网站优化步骤制作企业网站页面html
  • 南阳微网站推广wordpress 怎么添加网站备案信息
  • 将导出的数据导入新创建的海量数据库
  • 简单电商网站模板谷歌seo顾问
  • Vue3+Three.js:requestAnimationFrame的详细介绍
  • 找南阳建立网站的公司wordpress彻底禁用google
  • WHAT - 前端动画的前世今生
  • 技术视界 | 破解“1+1>2”的难题:多机器人系统的协同挑战与前沿突破
  • PPT宏代码
  • asp.net网站开发全过程淘宝客网站开发教程
  • 无锡网站优化推广东莞模板网站设计
  • 高级网站开发工程师什么是云速建站服务
  • 建动画网站需要多少钱德阳企业网站建设
  • SpringBootTest运行线程池被拒绝
  • 百度快照和做网站有关系吗企业网站网站建设电话
  • 高港做网站专业网站建设新闻
  • 网页网站设计公司排行榜高端网站创建
  • 工业时序数据分析:波动情况