两个数组的dp问题 - 实践

news/2025/11/9 11:38:03/文章来源:https://www.cnblogs.com/yxysuanfa/p/19203992

文章目录

  • 正则表达式匹配
  • 交错字符串
  • 两个字符串的最小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. 填表顺序
  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.mzph.cn/news/960358.shtml

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

相关文章

k8s级联删除策略

k8s级联删除策略前台级联删除:pod等从属对象删除之后,再删除所有者对象(比较慢)删除顺序:删除pod a -> 删除rs a-> 删除deploy a命令:kubectl delete deploy a --cascade=foreground 后台级联删除:删除所…

2025年口碑好的异形工业铝型材厂家最新推荐排行榜

2025年口碑好的异形工业铝型材厂家最新推荐排行榜行业背景与市场趋势工业铝型材作为现代工业制造的重要基础材料,在建筑、交通、电子、机械等领域应用广泛。根据中国有色金属工业协会最新数据显示,2024年中国铝型材市…

qemu+linux kernel+busybox搭建linux内核学习环境

前言:里面的知识很多理解的都不到位,不保证正确性,等后期学习好,再来修改。 前提:架构x86_64 一、准备工作sudo apt update sudo apt install build-essential qemu-system-x86 gdb git flex bison libncurses5-d…

2025年正规的电加热导热油炉厂家选购指南与推荐

2025年正规的电加热导热油炉厂家选购指南与推荐行业背景与市场趋势电加热导热油炉作为工业加热领域的关键设备,近年来随着环保政策趋严和能效标准提升,市场需求持续增长。根据中国锅炉行业协会2024年发布的《工业加热…

数据库原理与设计 - 教程

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025年口碑好的湘潭水泥支撑厂家推荐及选择参考

2025年口碑好的湘潭水泥支撑厂家推荐及选择参考行业背景与市场趋势水泥支撑行业作为建筑基础材料领域的重要组成部分,近年来随着我国基础设施建设的持续投入而保持稳定增长。据中国建筑材料联合会最新数据显示,2024年…

2025年正规的仪器计量校准厂家最新用户好评榜

2025年正规的仪器计量校准厂家最新用户好评榜行业背景与市场趋势仪器计量校准行业作为现代工业质量保障体系的重要支撑,近年来随着"中国制造2025"战略的深入推进和制造业转型升级的需求,市场规模持续扩大。…

AIGC|AI优化企业新榜单与选择指南 - 二当家

AI优化企业:2025年数字营销生态的变革者 解码AI优化企业如何重构流量生态与商业价值 AI优化企业——从技术穿透到全域增长的智能引擎 在2025年的数字营销战场,AI优化已从技术工具升维为商业生态重构的核心引擎。当传…

实用指南:# 深入理解Linux内核与用户态通信:Netlink机制实战

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

2025年质量好的养生托玛琳床垫TOP品牌厂家排行榜

2025年质量好的养生托玛琳床垫TOP品牌厂家排行榜行业背景与市场趋势随着健康生活理念的普及,养生寝具市场近年来呈现爆发式增长。据中国睡眠研究会2024年发布的《中国健康睡眠产业白皮书》显示,2023年中国功能性床垫…

S3D 模型对象权限检查

效果展示:代码如下public class CheckPG : BaseModalCommand{public override void OnStart(int instanceId, object argument){base.OnStart(instanceId, argument);if (ClientServiceProvider.SelectSet.Count == 0…

Python实现社交网络分析SNA公司董事数据与跨行业网络桥接识别可视化|附代码数据

全文链接:https://tecdat.cn/?p=44242 原文出处:拓端数据部落公众号 分析师:Song Yang引言 在数字化时代,企业间的关联早已超越股权与业务合作,董事作为核心决策参与者,其跨企业任职形成的社交网络逐渐成为资源…

2025年热门的铝制口红管子实力厂家TOP推荐榜

2025年热门的铝制口红管子实力厂家TOP推荐榜 行业背景与市场趋势 近年来,随着全球化妆品市场的持续增长,口红作为彩妆品类的核心产品之一,其包装需求也呈现显著上升趋势。据《2024全球化妆品包装市场报告》显示,…

架构篇:如何设计一个“看得懂、用得爽、管得好”还能“适度扩展”的系统?

嘿,各位开发者朋友们! 今天我们来聊一个每个程序员都绕不开的话题——软件架构。 一提到架构,你是不是立马想到了微服务、K8s、Service Mesh、高并发、高可用……这些高大上的词汇? 打住!🤚 对于绝大多数团队和…

2025年知名的角行程电动执行器行业内知名厂家排行榜

2025年知名的角行程电动执行器行业内知名厂家排行榜行业背景与市场趋势角行程电动执行器作为工业自动化控制系统的关键部件,近年来随着全球工业自动化水平的提升和智能制造的推进,市场需求持续增长。据国际权威市场研…

Nacos用法

目录业务概念1. 命名空间 (Namespace)2. Data ID (配置集 ID)3. Group (配置分组)🔑 总结关系参考资料 业务概念 1. 命名空间 (Namespace)作用: 用于租户级别的配置隔离。 定位: 最高级别的隔离。不同的命名空间下…

P4854 MloVtry的咸鱼树

推歌:Masquerade 传送 其实没什么难度,只要读懂题了就可以秒了。题意: 给定 \(n\) 个点 \(m\) 条边的无向图 \(G\),每条边有一个权值 \(w\) 和点集 \(S\)。 现在有一个点集 \(T\),初始只含有一个点。每次可以选择…

2025年靠谱的除四害专业好评推荐

2025年靠谱的除四害专业好评推荐:行业权威分析与优质服务商指南行业背景与市场趋势随着城市化进程加速和公共卫生意识提升,我国有害生物防治(PCO)行业已进入高质量发展阶段。据中国卫生有害生物防制协会最新数据显示…

ChatGPT Atlas 發佈了,但你真的需要嗎?

來寫寫瀏覽器使用的心路歷程吧,我沒有能力開發出屬於自己的瀏覽器,只能在這些產品中徘徊,最終我回到了原點,Firefox(下稱 FF,火狐)。 最開始,我還不會用 Google 的時候,我用的是 FF 中國的服務,但總是磕磕絆絆…

Python电动汽车充电网络优化研究——泊松过程、排队、贪心算法、模拟退火、聚类、差分演化DE、双目标动态规划、滚动时域预测控制MPC分析储能调度、电网负荷数据|附代码数据

全文链接:https://tecdat.cn/?p=44226原文出处:拓端数据部落公众号分析师:Zhichao Tong在“双碳”战略推进下,电动汽车保有量激增已成为必然趋势,而充电网络的资源闲置、布局失衡、负荷波动三大问题,正成为制约…