【十三】【动态规划】1745. 分割回文串 IV、132. 分割回文串 II、516. 最长回文子序列,三道题目深度解析

news2025/1/4 15:23:11

动态规划

动态规划就像是解决问题的一种策略,它可以帮助我们更高效地找到问题的解决方案。这个策略的核心思想就是将问题分解为一系列的小问题,并将每个小问题的解保存起来。这样,当我们需要解决原始问题的时候,我们就可以直接利用已经计算好的小问题的解,而不需要重复计算。

动态规划与数学归纳法思想上十分相似。

数学归纳法:

  1. 基础步骤(base case):首先证明命题在最小的基础情况下成立。通常这是一个较简单的情况,可以直接验证命题是否成立。

  2. 归纳步骤(inductive step):假设命题在某个情况下成立,然后证明在下一个情况下也成立。这个证明可以通过推理推断出结论或使用一些已知的规律来得到。

通过反复迭代归纳步骤,我们可以推导出命题在所有情况下成立的结论。

动态规划:

  1. 状态表示:

  2. 状态转移方程:

  3. 初始化:

  4. 填表顺序:

  5. 返回值:

数学归纳法的基础步骤相当于动态规划中初始化步骤。

数学归纳法的归纳步骤相当于动态规划中推导状态转移方程。

动态规划的思想和数学归纳法思想类似。

在动态规划中,首先得到状态在最小的基础情况下的值,然后通过状态转移方程,得到下一个状态的值,反复迭代,最终得到我们期望的状态下的值。

接下来我们通过三道例题,深入理解动态规划思想,以及实现动态规划的具体步骤。

1745. 分割回文串 IV - 力扣(LeetCode)

题目解析

状态表示

状态表示通常由经验+题目要求得到,

经验一般指以某个位置为结尾,或者以某个位置为开始。

我们需要判断(i,j)子数组是否属于回文子数组,可以定义dp[i][j]表示(i,j)子数组是否属于回文子数组。

状态转移方程

我们希望(i,j)位置的状态能够通过其他位置的状态推导出来。

针对于(i,j)位置的状态进行分析。

  1. 如果nums[i]==nums[j],

    1. 如果(i,j)子数组只有一个元素,即i==j, 只有一个元素属于回文子数组情况,故dp[i][j]=true。

    2. 如果(i,j)子数组只有两个元素,即i+1==j, 此时符合回文子数组的定义,故dp[i][j]=true。

    3. 如果(i,j)子数组有3个或3个以上的元素,即i+1<j, 此时,如果(i+1,j-1)子数组可以构成回文子数组,那么(i,j)子数组就可以构成回文子数组。 故,dp[i][j]=dp[i+1][j-1]。

  2. 如果nums[i]!=nums[j], 此时不可能构成回文子数组,所以dp[i][j]=false。

对上述情况进行合并和简化,

如果我们对所有位置状态初始化为false,我们就只需要判断nums[i]==nums[j]的情况,

此时的状态转移方程为,

 
       if(s[i]==s[j]){
               dp[i][j] = i + 1 < j ? dp[i + 1][j - 1] : true;
       }

初始化

根据状态转移方程,我们知道推导(i,j)位置的状态时,可能需要用到dp[i+1][j-1]位置的状态, 如果i+1<j,此时需要保证(i+1,j-1)对应下标不会越界,并且此时(i+1,j-1)位置状态已经填写完毕。

先考虑填表顺序,我们知道i介于(0,n-1)之间,j介于(i,n-1)之间,所以i+1介于(1,n)之间且i+1<j,所以i+1介于(1,n-1)之间,i+1不会越界。

又j介于(i,n-1)之间,j-1介于(i-1,n-2)之间,又j-1>i,所以j-1也不会越界。

要保证此时(i+1,j-1)位置状态已经填写完毕,只需要控制填报顺序即可。

所以我们需要初始化所有位置状态为false即可,也就是在状态转移方程中分析的初始化。

填表顺序

根据状态转移方程,我们知道推导(i,j)位置的状态时,可能需要用到dp[i+1][j-1]位置的状态, 所以在填写(i,j)位置状态时,需要保证(i+1,j-1)位置状态已经填写完毕。

  1. 如果固定i填写j, 那么i的变化一定要从大到小,此时当我们填写(i,j)位置的状态时,(i+1,)位置的状态已经填写完毕,所以j的变化可以从大到小也可以从小到大。

  2. 如果固定j填写i, 那么j的变化一定要从小到大,此时当我们填写(i,j)位置的状态时,(,j-1)位置的状态已经填写完毕,所以i'的变化可以从大到小也可以从小到大。

如果我们选择固定i填写j,得到

 
    for(int i=n-1;i>=0;i--){
            for(int j=i;j<=n-1;j++){
            
            }
    }

返回值

dp[i][j]表示(i,j)子数组是否属于回文子数组。

dp状态的填写只完成了第二步的工作,即快速判断(i,j)子数组是否为回文子数组。

还有一步,就是使(a,b)遍历所有情况。

如果有一种情况三部分都是回文子数组,就返回true,否则就返回false。

代码实现

 
class Solution {
public:
    bool checkPartitioning(string s) {

        int n = s.size();
        vector<vector<bool>> dp(n, vector<bool>(n));
        for (int i = n - 1; i >= 0; i--)
            for (int j = i; j < n; j++)
                if (s[i] == s[j])
                    dp[i][j] = i + 1 < j ? dp[i + 1][j - 1] : true;

        for (int i = 1; i < n - 1; i++)
            for (int j = i; j < n - 1; j++)
                if (dp[0][i - 1] && dp[i][j] && dp[j + 1][n - 1])
                    return true;
        return false;
    }
};

132. 分割回文串 II - 力扣(LeetCode)

题目解析

状态表示

状态表示通常由经验+题目要求得到,

经验一般指以某个位置为结尾,或者以某个位置为开始。

我们很容易可以定义这样一个状态表示,定义dp[i]表示在(0,i)区间上的字符串,最少的分割次数。

状态转移方程

我们针对于最后一个位置的状态进行分析,看看i位置状态能不能由其他位置的状态推导得出,定义0<=j<=i,那我们可以根据(j,i)位置上的子串是否是回文串分成下面两种情况,

  1. 如果(j,i)可以构成回文串, i位置的状态就等于j-1位置上的状态+1,即dp[i]=dp[j-1]+1。

  2. 如果(j,i)不能构成回文串, 此时j的位置不需要考虑。

因为dp[i]要的是最小的分割次数,所以j需要遍历(0~i-1)。

因为我们需要快速判断(j,i)位置是否属于回文字符串,所以我们可以先创建一个dp表,dp[i][j]表示(i,j)字符串是否构成回文字符串。用来存储是否可以构成回文字符串的信息。

根据上述分析,我们知道要推导i位置的状态,可能需要用到j-1位置的状态。

所以i的变化应该是从小到大,即(0~n-1)。

令j-1>=0得j>=1,只有j>=1的时候才不会越界。所以我们需要控制j介于(1,i)之间。

独立判断(0,i)这种情况。

所以状态转移方程为,

 
        for (int i = 0; i < n; i++) {
            if (isPal[0][i])
                dp[i] = 0;
            else {
                for (int j = 1; j <= i; j++)
                    if (isPal[j][i])
                        dp[i] = min(dp[i], dp[j - 1] + 1);
            }
        }

初始化

带入最初始的推导,即i=0,发现dp[i] 可以正常推导,而后续的状态都可以根据前面已经推导的状态进行推导得出,所以不需要进行初始化。

填表顺序

从左往右

返回值

dp[i]表示在(0,i)区间上的字符串,最少的分割次数。

题目要求我们找到(0,n-1)区间上的字符串,最少的分割次数,

所以返回dp[n-1]即可。

代码实现

 
class Solution {
public:
    int minCut(string s) {
        int n = s.size();
        vector<vector<bool>> isPal(n, vector<bool>(n));
        for (int i = n - 1; i >= 0; i--)
            for (int j = i; j < n; j++)
                isPal[i][j] = s[i] == s[j] ? (i + 1 < j ? isPal[i + 1][j - 1] : true): false;
        vector<int> dp(n, INT_MAX);
        for (int i = 0; i < n; i++) {
            if (isPal[0][i])
                dp[i] = 0;
            else {
                for (int j = 1; j <= i; j++)
                    if (isPal[j][i])
                        dp[i] = min(dp[i], dp[j - 1] + 1);
            }
        }
        return dp[n - 1];
    }
};

516. 最长回文子序列 - 力扣(LeetCode)

题目解析

状态表示

状态表示一般通过经验+题目要求得到,

经验一般指以某个位置为结尾,或者以某个位置为开始。

题目要求我们找回文子序列,根据以前的经验,和回文有关的问题,我们的状态表示研究的对象一

般都是选取原字符串中的一段区域[i,j]内部的情况来研究。

所以我们可以定义dp[i][j]表示s字符串[i,j]区间内所有子序列中,最长的回文序列长度。

状态转移方程

我们针对于最后一个位置的状态进行分析,看看i位置状态能不能由其他位置的状态推导得出。

如果紫色的字符串可以构成回文串,那么我们在紫色字符串两端添加相同的蓝色元素,整个字符串同样可以构成回文串。

根据这种思维,我们可以根据i,j两个位置的元素是否相等进行分析。

  1. 如果s[i]==s[j], 那么[i,j]区间上的最长回文子序列,应该是 [i+1,j-1] 区间上的最长回文子序列首尾加上s[i],s[j]两个元素,此时dp[i][j]=dp[i+1][j-1]+2。

  2. 如果s[i]!=s[j], 说明[i,j]区间上的回文子序列不可能同时取到i,j两个位置上的元素。

    1. 如果i位置元素不取, 此时[i,j]区间上的最长回文子序列的长度,应该是[i+1,j]区间上的最长回文子序列的长度。即dp[i][j]=dp[i+1][j]。

    2. 如果j位置元素不取, 此时[i,j]区间上的最长回文子序列的长度,应该是[i,j-1]区间上的最长回文子序列的长度。即dp[i][j]=dp[i][j-1]。

第二种情况下,dp中存储的是最长的回文子序列的长度,所以dp[i][j]=max(dp[i+1][j],dp[i][j-1])。

综上所述,状态转移方程为,

s[i] == s[j] 时: dp[i][j] = dp[i + 1][j - 1] + 2 s[i] != s[j] 时: dp[i][j] = max(dp[i][j - 1],dp[i + 1][j])

初始化

根据状态转移方程,我们知道想要推导(i,j)位置的状态,可能需要用到(i+1,j-1),(i,j-1),(i+1,j)位置上的状态。

我们先判断填表顺序,

  1. 如果固定i改变j, 那么i的变化一定从大到小,因为可能用到(i,j-1)位置的状态,所以j的变化需要从小到大。

  2. 如果固定j改变i, 那么j的变化一定从小到大,因为可能用到(i+1,j)位置的状态,所以i的变化需要从大到小。

所以我们可以得到完整的状态转移方程,

 
        for (int i = n - 1; i >= 0; i--)
        {
            dp[i][i] = 1;                   
            for (int j = i + 1; j < n; j++)
            {
              
                if (s[i] == s[j])
                    dp[i][j] = dp[i + 1][j - 1] + 2;
                else
                    dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
            }
        }

我们把最初迭代情况带入迭代代码中,即i=n-1,j=n,此时得到dp[n-1][n-1]=1。

而后续的状态都可以根据前面的状态推导得出,所以我们不需要进行初始化。

填表顺序

  1. 如果固定i改变j, 那么i的变化一定从大到小,因为可能用到(i,j-1)位置的状态,所以j的变化需要从小到大。

  2. 如果固定j改变i, 那么j的变化一定从小到大,因为可能用到(i+1,j)位置的状态,所以i的变化需要从大到小。

返回值

dp[i][j]表示s字符串[i,j]区间内所有子序列中,最长的回文序列长度。

根据题目要求,我们需要得到[0,n-1]区间内所有子序列中,最长的回文子序列长度。

所以返回dp[0][n-1]。

代码实现

 
class Solution {
public:
    int longestPalindromeSubseq(string s) {
        int n = s.size();
        vector<vector<int>> dp(n, vector<int>(n)); 

        for (int i = n - 1; i >= 0; i--)
        {
            dp[i][i] = 1;                   
            for (int j = i + 1; j < n; j++)
            {
              
                if (s[i] == s[j])
                    dp[i][j] = dp[i + 1][j - 1] + 2;
                else
                    dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
            }
        }
    
        return dp[0][n - 1];
    }
};

结尾

今天我们学习了动态规划的思想,动态规划思想和数学归纳法思想有一些类似,动态规划在模拟数学归纳法的过程,已知一个最简单的基础解,通过得到前项与后项的推导关系,由这个最简单的基础解,我们可以一步一步推导出我们希望得到的那个解,把我们得到的解依次存放在dp数组中,dp数组中对应的状态,就像是数列里面的每一项。最后感谢您阅读我的文章,对于动态规划系列,我会一直更新,如果您觉得内容有帮助,可以点赞加关注,以快速阅读最新文章。

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

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

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

相关文章

半年没发过文章,这个博主竟然...

目 录 前言这半年去干了什么&#xff1f;考研求职山东电建移动 论文大创课余 未来公务员继续考研就业 结语 前言 第一次写这样的记录性的文章&#xff0c;这篇文章可能不会有太过于华丽的辞藻&#xff0c;但是它将展现我个人的真实经历和内心感受。在过去的时间里&#xff0c;我…

WEB 3D技术 three.js通过光线投射 完成几何体与外界的事件交互

本文 我们来说 光线投射 光线投射技术是用于3维空间场景中的交互事件 我们先编写代码如下 import ./style.css import * as THREE from "three"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";const scene new THRE…

Redis Cluster集群模式学习

Redis Cluster集群模式 Redis哨兵模式&#xff1a;https://blog.csdn.net/liwenyang1992/article/details/133956200 Redis Cluster集群模式示意图&#xff1a; Cluster模式是Redis3.0开始推出采用无中心结构&#xff0c;每个节点保存数据和整个集群状态&#xff0c;每个节点都…

【python】Python 3.11不支持Tix库

Tix库主要用于扩展Tkinter&#xff0c;但是Python 3.11 Tkinter已经不再支持Tix库。Tix模块提供了一些额外的部件和功能&#xff0c;但现在这些功能已经整合到了Tkinter库中。 一、如果在Python 3.11中想要使用Tix库&#xff0c;但发现它不再被内置支持&#xff0c;可以尝试以…

使用Wireshark进行网络流量分析

目录 Wireshark是什么&#xff1f; 数据包筛选 筛选指定ip 使用逻辑运算符筛选 HTTP模式过滤 端口筛选 协议筛选 包长度筛选 数据包搜索 数据流分析 数据包导出 Wireshark是什么&#xff1f; 通过Wireshark&#xff0c;我们可以捕获和分析网络数据包&#xff0c;查看…

ARM CCA机密计算硬件架构之内存管理

实施了TrustZone安全扩展的Arm A-profile处理器呈现两个物理地址空间(PAS): 非安全物理地址空间安全物理地址空间Realm管理扩展增加了两个PAS: Realm物理地址空间Root物理地址空间下图显示了这些物理地址空间以及如何在工作系统中实施这些空间: 正如表格所示,根状态能够访…

2024,启动(回顾我的2023)

零.前言 打开博客想写个年度总结&#xff0c;发现已经半年没有更新文章了&#xff0c;排名从几千掉到了几万&#xff0c;不过数据量还是不错的。 时间过得可真快&#xff0c;2023年是充满动荡的一年&#xff0c;上半年gpt横空出世&#xff0c;下半年各种翻车暴雷吃瓜吃到嘴软…

计算机毕业设计选题分享-Springboot在线问诊系统00211(赠送源码数据库)JAVA、PHP,node.js,C++、python,大屏数据可视化等

Springboot在线问诊系统 摘 要 针对医院门诊等问题&#xff0c;对在线问诊进行研究分析&#xff0c;然后开发设计出在线问诊系统以解决问题。在线问诊系统主要功能模块包括首页、轮播图管理、公告信息管理、资源管理、系统用户管理&#xff08;管理员、患者用户、医生用户&…

golang并发编程-channel

在golang 并发编程里&#xff0c;经常会听到一句话&#xff1a;不要通过共享内存进行通信&#xff0c;通过通信来共享内存。下面我们会介绍下channel, 通过源码的方式去了解channel是怎么工作的。 基本结构 流程图 代码解读 type hchan struct {qcount uint // …

【银行测试】超细支付功能测试+测试点总结分析(详全)

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、支付功能怎么测…

从零开始:使用 BIND 构建和管理您的 DNS 服务器

1 前言 在这篇文章中&#xff0c;我将详细介绍如何使用 BIND&#xff08;Berkeley Internet Name Domain&#xff09;软件包中的 named 程序来配置和管理一个基本的 DNS 服务器。 从安装 BIND 开始&#xff0c;到设置 DNS 区域文件&#xff0c;再到运行和测试您的服务器&#x…

ROS学习记录:ROS系统中的激光雷达消息包的数据格式

一、在工作空间中输入source ./devel/setup.bash 二、输入roslaunch wpr_simulation wpb_simple.launch打开机器人仿真环境 三、机器人仿真环境打开成功 四、给机器人围上一圈障碍物 五、再打开一个工作空间终端 六、输入roslaunch wpr_simulation wpb_rviz.launch打开RViz 七、…

Node.js使用jemalloc内存分配器显著减少内存使用

前言 Node.js 默认使用的是 ptmalloc(glibc) 内存分配器&#xff0c;而&#xff1a; 在服务端领域「不会选择默认的 malloc」是一个常识。&#xff08; 来源 &#xff09; ptmalloc 的分配效率较低&#xff08; 来源 &#xff09;&#xff0c;对于 长时间、多核 / 多线程 运行…

手机录屏没有声音?让你的录屏有声有色

“有人知道手机录屏怎么录声音吗&#xff1f;今天录制了一个小时的直播视频&#xff0c;后面查看的时候发现没有声音&#xff0c;真的非常崩溃&#xff0c;想问问大家有没有办法&#xff0c;解决这个问题。” 在手机录屏的过程中&#xff0c;有时候我们可能会面临录制视频没有…

第19届楚天春晚在九省通衢大武汉闪亮登场

——中国明星后代女子歌舞团刘亦菲等兼任形象大使 2024年新年伊始&#xff0c;一场以繁荣中国传统文化为主旨的“楚天春晚”全国文旅活动在湖北武汉拉开帷幕&#xff01;以“文化强省&#xff0c;旅游大省”为神州特色的联合国世界品牌之都&#xff0c;在辞旧迎新的早春来临之…

手机摄影学习

手机摄影学习 基础知识1&#xff0c;成像基本原理2&#xff0c;什么是焦距3&#xff0c;快门&#xff08;简称s&#xff09;4&#xff0c;上面功能之间的相互影响5&#xff0c;焦点6&#xff0c;过爆、欠曝7&#xff0c;cmos&#xff08;感光芯片&#xff09;、测光、聚焦&…

考研用什么光源比较好?五款好用台灯推荐

人眼对光是非常敏感的&#xff0c;特别是儿童青少年眼睛还在发育的状态来说&#xff0c;光线是至关重要的&#xff0c;于是这次测评就不能马虎&#xff0c;必须要本着专业严谨的态度进行测评&#xff0c;这次测评呢就花了不少钱买下了现在市面上热度很高、或是较有名气的专业款…

贝锐花生壳全新功能:浏览器一键远程访问SSHRDP远程桌面

为了满足特定场景的远程访问需求&#xff0c;如&#xff1a;远程群晖NAS设备、远程SQL Server数据库/MySQL数据库、3389远程桌面&#xff08;RDP远程桌面&#xff09;、远程SSH、我的世界游戏联机…… 贝锐花生壳推出了场景映射服务&#xff0c;不仅提供满足相应场景的网络带宽…

机器人活动区域 - 华为OD统一考试

OD统一考试 题解: Java / Python / C++ 题目描述 现有一个机器人,可放置于 M x N 的网格中任意位置,每个网格包含一个非负整数编号,当相邻网格的数字编号差值的绝对值小于等于 1 时机器人可以在网格间移动。 问题: 求机器人可活动的最大范围对应的网格点数目。 说明: 网格…

剑指“CPU飙高”问题

一、什么是cpu飙高&#xff1f; 一般指程序运行时cpu占用率过高   linux系统中&#xff0c;我们使用top命令&#xff0c;会看到正在运行进程的cpu使用率等&#xff0c;同时在最上面也会看到总的cpu使用率&#xff0c;当总的cpu使用率过高&#xff0c;如果有运维监控平台&…