动态规划课堂6-----回文串问题

news2024/12/26 20:56:14

目录

引言:

例题1:回文子串

例题2:回文串分割IV

例题3:分割回文串II

例题4:最长回文子序列

例题5:让字符串成为回文串的最小插入次数


引言:

回文字符串 是正着读和倒过来读一样的字符串。

动态规划的回文串问题一般是把子串是否是回文串的信息保持在dp表里面,所以更多的时候回文串的dp表只是起到一个辅助的作用,有一些题要利用回文串dp表再做一次动态规划,其实很多困难题某一些步骤都是可以动态规划来化简的。😎😎😎

例题1:回文子串

链接:回文子串

题目简介:

给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。

回文字符串 是正着读和倒过来读一样的字符串。

子字符串 是字符串中的由连续字符组成的一个序列。

具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。

解法(动态规划):

当然这题的最优解不是动态规划而是中心拓展算法,但是我们这里主要叙述动态规划。

这一题其实就是一个把回文子串信息保存在dp表里面的模板题😎

对于本题我们可以先预处理⼀下,将所有子串是否回⽂的信息统计在dp 表⾥⾯,然后直接在表里面统计true 的个数即可。

 1. 状态表示:

dp[i][j] 表示: s 字符串[i, j] 的子串,是否是回文串。

这个二维的dp表其实只需用到上三角的地方,因为j是大于等于i的。

 2.状态转移方程:

对于回文串,我们⼀般分析⼀个区间两头的元素:例如下图利用最外层和内层的递推关系完成动态规划。

(1)当s[i] != s[j] 的时候:不可能是回文串, dp[i][j] = 0 ; 

(2)当s[i] == s[j] 的时候:根据长度分三种情况讨论:

1.⻓度为1 ,也就是i == j ,此时⼀定是回文串, dp[i][j] = true。

2.⻓度为2 ,也就是i + 1 == j :此时也⼀定是回⽂串, dp[i][j] = true 。

3.⻓度⼤于2 ,此时要去看看[i + 1, j - 1] 区间的子串是否回文: dp[i][j] = dp[i + 1][j - 1] 。

 3.初始化:

无需初始化,因为我们填表的范围如下图,会越界的节点在对角线,但是我们上面的状态转移方程已经把i == j的情况特判了,所以不会越界。

 4.填表顺序:

从下往上

 5.返回值:

返回dp 表中true 的个数。

代码如下:

dp[i][j] = i + 1 < j ? dp[i + 1][j - 1] : true;解释如下:因为i == j 和i + 1 == j 的情况下dp[i][j]都为true,长度大于2对应i + 1 < j 的情况。

class Solution {
    public int countSubstrings(String ss) {
        //1.创建 dp 表
        //2.初始化
        //3.填表
        //4.返回值
        int n = ss.length();
        char[] s = ss.toCharArray();
        boolean[][] dp = new boolean[n][n];
        int count = 0;
        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;
                }
                if(dp[i][j] == true){
                    count++;
                }
            }
        }
        return count;

    }
}

时间复杂度:O(n^2)

空间复杂度:O(n^2)

下面我给出一题大家练练手,解法过程和例题1差不多就是最后创建完回文dp表最后的返回值不一样。最长回文子串

例题2:回文串分割IV

链接:回文串分割IV

题目简介:

给你一个字符串 s ,如果可以将它分割成三个 非空 回文子字符串,那么返回 true ,否则返回 false 。

当一个字符串正着读和反着读是一模一样的,就称其为 回文字符串 。

关于预处理所有子串是否回文,已经在上⼀道题目里面讲过,这里就不再赘述啦~

先把回文dp表填好,接下来枚举三个子串除字符串端点外的起止点,查询这三段非空子串是否是回文串。 枚举i和j这两条分界线即可,注意区间的闭合问题和J必须要大于等于i。

代码如下:

1. 利用 dp 处理⼀下所有的子串是否回文。

2. 枚举第二个字符串所有的起始位置和终止位置。

写代码希望大家按照1.创建 dp 表2,初始化,3.填表,4.返回值的顺序来进行书写代码,这样不会乱。

class Solution {
    public boolean checkPartitioning(String s) {
        //1.创建 dp 表
        //2.初始化
        //3.填表
        //4.返回值
        int n = s.length();
        boolean[][] dp = new boolean[n][n];
        for(int i = n - 1;i >= 0;i--){
            for(int j = i;j < n;j++){
                if(s.charAt(i) == s.charAt(j)){
                    dp[i][j] = i + 1 < j ? dp[i + 1][j - 1] : true;
                }
            }
        }
        boolean ans = false;
        for(int i = 1;i < n;i++){
            for(int j = i;j < n - 1;j++){
                if(dp[0][i - 1] && dp[i][j] && dp[j + 1][n - 1]){
                    ans = true;
                    return ans;
                }

            }
        }
        return false;

    }
}

时间复杂度:O(n^2)

空间复杂度:O(n^2)

例题3:分割回文串II

链接:分割回文串II

题目简介:

给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文串。

返回符合要求的 最少分割次数 。

  解法(动态规划):

 1. 状态表示:

dp[i] 表示: s中[0, i] 区间上的字符串,最少分割的次数。

 2.状态转移方程:

状态转移方程⼀般都是根据最后⼀个位置的信息来分析:设0 <= j <= i ,那么我们可以根据j ~ i位置上的子串是否是回文串分成下面两类:

(1)当[j ,i] 位置上的子串能够构成⼀个回文串,那么dp[i] 就等于[0, j - 1] 区间上最少回文串的个数+1,即dp[i] = dp[j - 1] + 1 。

(2)当[j ,i] 位置上的⼦串不能构成⼀个回文串,此时j 位置就不⽤考虑。

由于我们要的是最小值,因此应该循环遍历⼀遍j的取值,拿到里面的最小值即可。

优化:我们在状态转移⽅程里面分析到,要能够快速判读字符串里面的⼦串是否回文。因此,我们 可以先处理⼀个dp 表,里面保存所有⼦串是否回文的信息。

 3.初始化:

为了防止求min操作时,0干扰结果。我们先把表里面的值初始化为无穷大。

 4.填表顺序:

从左往右

 5.返回值:

应该返回dp[n - 1]。

代码如下:

其中algorithm就是回文串信息的dp表。

class Solution {
    public int minCut(String s) {
        //1.创建 dp 表
        //2.初始化
        //3.填表
        //4.返回值
        int n = s.length();
        int[] dp = new int[n];
        boolean[][] algorithm = new boolean[n][n];
        for(int i = n - 1;i >= 0;i--){
            for(int j = i;j < n;j++){
                if(s.charAt(i) != s.charAt(j)){
                    algorithm[i][j] = false;
                }else{
                    algorithm[i][j] = i + 1 < j ? algorithm[i + 1][j - 1] : true;
                }
            }
        }
        for(int i = 0;i < n;i++){
            dp[i] = 0x3f3f3f3f;
        }
        for(int i = 0;i < n;i++){
            for(int j = 0;j <= i;j++){
                if(algorithm[0][i]){
                    dp[i] = 0;
                    break;
                }else{
                    if(algorithm[j][i]){
                        dp[i] = Math.min(dp[i],dp[j - 1] + 1);
                    }
                }
            }
        }
        return dp[n - 1];
    }
}

时间复杂度:O(n^2)

空间复杂度:O(n^2)

例题4:最长回文子序列

链接:最长回文子序列

题目简介:

给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。

子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。

 解法(动态规划):

 1. 状态表示:

回文串问题一般取左右区间,以左右节点来分析。

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

 2.状态转移方程:

关于回文子序列和回文子串的分析⽅式,⼀般都是⽐较固定的,都是选择这段区域的左右端点的字符情况来分析。因为如果⼀个序列是回文串的话,去掉⾸尾两个元素之后依旧是回⽂串,⾸尾加上两个相同的元素之后也依旧是回文串。因为,根据首尾元素的不同,可以分为下面两种情况:

(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] :此时这两个元素就不能同时添加在⼀个回⽂串的左右,那么我们就应该让s[i] 单独加在⼀个序列的左边,或者让s[j] 单独放在⼀个序列的右边,看看这两种情况下的最大值:

1.单独加⼊s[i] 后的区间在[i, j - 1] ,此时最长的回⽂序列的长度就是dp[i] [j - 1] 。

2.单独加⼊s[j] 后的区间在[i + 1, j] ,此时最长的回⽂序列的长度就是dp[i + 1][j] 。

取两者的最大值,于是dp[i][j] = max(dp[i][j - 1], dp[i + 1][j])。

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

(1)当s[i] == s[j] 时: dp[i][j] = dp[i + 1][j - 1] + 2 .

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

 3.初始化:

根据状态转移⽅程dp[i][j] = dp[i + 1][j - 1] + 2 ,我们状态表⽰的时候,选取的是⼀段区间,因此需要要求左端点的值要⼩于等于右端点的值,因此会有两种边界情况:

(1)当i == j 的时候, i + 1 就会大于j - 1 ,此时区间内只有⼀个字符。这个⽐较好分析, dp[i][j] 表示⼀个字符的最长回文序列,⼀个字符能够自己组成回文串,因此此时dp[i][j] = 1。

(2)当i + 1 == j的时候, i + 1 也会大于j - 1 ,此时区间内有两个字符。这样也好分析,当这两个字符相同的时候, dp[i][j] = 2 ;不相同的时候, d[i][j] = 0.

对于第⼀种边界情况,我们在填表的时候,就可以同步处理。

对于第⼆种边界情况, dp[i + 1][j - 1] 的值为0 ,不会影响最终的结果,因此可以不用考虑。

 4.填表顺序:

从下往上填写每⼀行。

每⼀行从左往右。

 5.返回值: 

根据状态表示,我们需要返回[0, n -1] 区域上的最长回文序列的长度,因此需要返回dp[0][n - 1] 。

代码如下:

class Solution {
    public int longestPalindromeSubseq(String s) {
        //.创建 dp 表
        //2.初始化
        //3.填表
        //4.返回值
        int n = s.length();
        int[][] dp = new int[n][n];
        for(int i = n - 1;i >= 0;i--){
            //j必须要大于等于i
            for(int j = i;j < n;j++){
                if(s.charAt(i) == s.charAt(j)){
                    if(i == j){
                        dp[i][j] = 1;
                    }else if(i + 1 == j){
                        dp[i][j] = 2;
                    }else{
                        dp[i][j] = dp[i + 1][j - 1] + 2;
                    }
                }else{
                    dp[i][j] = Math.max(dp[i][j - 1],dp[i + 1][j]);
                }
            }
        }
        return dp[0][n - 1];
    }
}

时间复杂度:O(n^2)

空间复杂度:O(n^2)

例题5:让字符串成为回文串的最小插入次数

链接:让字符串成为回文串的最小插入次数

题目简介:

给你一个字符串 s ,每一次操作你都可以在字符串的任意位置插入任意字符。

请你返回让 s 成为回文串的 最少操作次数 。

回文串是正读和反读都相同的字符串。

  解法(动态规划):

 1. 状态表示:

状态表示: dp[i][j]表示字符串[i, j] 区域成为回文子串的最少插入次数。

 2.状态转移方程:

根据首尾元素的不同,可 以分为下⾯两种情况:

(1)当首尾两个元素相同的时候,也就是s[i] == s[j] :

1.那么 [i, j] 区间内成为回文子串的最少插⼊次数,取决于[i + 1, j - 1] 区间内成为回文子串的最少插入次数。

2.若 i == j 或i == j - 1( [i + 1, j - 1] 不构成合法区间),此时只有1 ~2个相同的字符, [i, j] 区间⼀定是回文子串,成为回文子串的最少插入次数是0.

(2)当首尾两个元素不相同的时候,也就是s[i] != s[j] :

1.此时可以在区间最右边补上⼀个 s[i] ,需要的最少插入次数是 [i + 1, j] 成为回文子串的最少插⼊次数+本次插入,即 dp[i][j] = dp[i + 1][j] + 1 。

2.此时可以在区间最左边补上⼀个 s[j] ,需要的最少插入次数是 [i, j + 1] 成为回文子串的最少插⼊次数+本次插⼊,即 dp[i][j] = dp[i][j + 1] + 1 。

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

(1)当s[i] == s[j]时: dp[i][j] = i >= j - 1 ? 1 : dp[i + 1][j - 1]。

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

 3.初始化:

没有不能递推表示的值,无需初始化。

 4.填表顺序:

从下往上填写每⼀行

从下往上填写每⼀行

 5.返回值:

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

代码如下:

class Solution {
    public int minInsertions(String s) {
        //1.创建 dp 表
        //2.初始化
        //3.填表
        //4.返回值
        int n = s.length();
        int[][] dp = new int[n][n];
        for(int i = n - 1;i >= 0;i--){
            for(int j = i;j < n;j++){
                if(s.charAt(i) == s.charAt(j)){
                    dp[i][j] = i + 1 < j ? dp[i + 1][j - 1] : 0;
                }else{
                    dp[i][j] = Math.min(dp[i + 1][j] + 1,dp[i][j - 1] + 1);
                }
            }
        }
        return dp[0][n - 1];
    }
}

时间复杂度:O(n^2)

空间复杂度:O(n^2)

结语:

其实写博客不仅仅是为了教大家,同时这也有利于我巩固知识点,和做一个学习的总结,由于作者水平有限,对文章有任何问题还请指出,非常感谢。如果大家有所收获的话还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。

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

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

相关文章

Sora后时代文生视频的探索

一、写在前面 按常理&#xff0c;这里应该长篇大论地介绍一下Sora发布对各行业各方面产生的影响。不过&#xff0c;这类文章已经很多了&#xff0c;我们今天主要聊聊那些已经成熟的解决方案、那些已经可以“信手拈来”的成果&#xff0c;并以此为基础&#xff0c;看看Sora发布…

PHP全新美化广告横幅在线制作源码

源码简介 可以做网站的引流不需要安装上传就可以使用&#xff0c;在第一版基础上做了二次开发更加好用 注意&#xff1a;主机和服务器均可架设搭建,如果使用宝塔架设点击访问的时候提示找不到文件路径的时候,记得点击网站目录把防跨站攻击先关闭,这样就可以正常访问了,这款是…

用pdf2docx将PDF转换成word文档

pdf2docx是一个Python模块&#xff0c;可以将PDF文件转换为docx格式的Word文档。 pdf2docx模块基于Python的pdfminer和python-docx库开发&#xff0c;可以在Windows、Linux和Mac系统上运行。它可以从PDF文件中提取文本和图片&#xff0c;并将其转换成可编辑的Word文档&#xf…

手撕算法-二叉搜索树与双向链表

牛客BM30。 描述&#xff1a;https://www.nowcoder.com/practice/947f6eb80d944a84850b0538bf0ec3a5?tpId295&tqId23253&ru/exam/oj&qru/ta/format-top101/question-ranking&sourceUrl%2Fexam%2Foj分析&#xff1a;二叉搜索树的中序遍历是递增序列。可以利用…

相聚武汉氢能展_2024武汉国际氢能源及燃料电池产业博览会

相聚武汉氢能展_2024武汉国际氢能源及燃料电池产业博览会 2024武汉国际氢能源及燃料电池产业博览会 2024 Wuhan International Hydrogen Energy and Fuel Cell Industry Expo 同期举办&#xff1a;2024世界汽车制造技术暨智能装备博览会 时间&#xff1a;2024.8.14-16 地…

【动态规划】【同余前缀和】【多重背包】[推荐]2902. 和带限制的子多重集合的数目

本文涉及知识点 动态规划汇总 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 C算法&#xff1a;滑动窗口总结 多重背包 LeetCode2902. 和带限制的子多重集合的数目 给你一个下标从 0 开始的非负整数数组 nums 和两个整数 l 和 r 。 请你…

拷贝他人maven仓库jar包到自己本地仓库,加载maven依然提示无法下载对应依赖

所遇问题&#xff1a; 拷贝他人maven仓库jar包到自己本地maven仓库repository下的对应依赖位置&#xff0c;重新加载idea的maven依然提示无法下载对应依赖。 解决办法&#xff1a; 在maven->repository找到对应报错依赖路径&#xff0c;删除xxx.repositories 和 xxx.lastU…

记录开发STM32遇到的卡死问题-串口

背景&#xff1a;以STM32作为主控&#xff0c;广州大彩显示屏显示&#xff0c;主控实时采集数据&#xff0c;串口波特率115200.设置收发频率为50Hz&#xff0c;即单片机每秒发送50帧数据&#xff0c;每秒接收50帧数据&#xff0c;每帧数据大概14字节。 问题&#xff1a;系统长…

Beamer模板——基于LaTeX制作学术PPT

Beamer模板——基于LaTeX制作学术PPT 介绍Beamer的基本使用安装和编译用于学术汇报的模板项目代码模板效果图 Beamer的高级特性动态效果分栏布局定理环境 介绍 在学术领域&#xff0c;演示文稿是展示和讨论研究成果的重要方式。传统的PowerPoint虽然方便&#xff0c;但在处理复…

C#中右键通过listview来控制datagridview字段值的是否显示、显示顺序,并存储到XML中。

最终显示效果&#xff0c;如下图所示&#xff1a; datagridview开始显示通过调用XML存储的字段值及顺序来显示&#xff0c;右键调出Tools来控制显示的顺序及是否显示&#xff0c;通过加号和减号进行调整顺序。 XML存储字段值及顺序 主要代码及事件&#xff1a; 获取datagridv…

SG5032VAN差分晶振X1G004261001100专用于5G通讯设备

差分晶体振荡器(DXO)是目前行业中公认高技术&#xff0c;高要求的一款晶体振荡器&#xff0c;是指输出差分信号使用2种相位彼此完全相反的信号,从而消除了共模噪声,并产生一个更高性能的系统。差分晶振一般为六脚贴片晶振&#xff0c;输出类型分为好几种,LVDS&#xff0c;LV-PE…

责任链模式(处理逻辑解耦)

前言 使用设计模式的主要目的之一就是解耦&#xff0c;让程序易于维护和更好扩展。 责任链则是将处理逻辑进行解耦&#xff0c;将独立的处理逻辑抽取到不同的处理者中&#xff0c;每个处理者都能够单独修改而不影响其他处理者。 使用时&#xff0c;依次调用链上的处理者处理…

前后端分离项目springsecurity实现用户登录认证快速使用

目录 1、引入依赖 2、创建类继承WebSecurityConfigurerAdapter &#xff08;1&#xff09;重写里面的configure(HttpSecurity http)方法 &#xff08;2&#xff09;重写AuthenticationManager authenticationManagerBean() &#xff08;3&#xff09;密码加密工具 3、继承…

P8597 [蓝桥杯 2013 省 B] 翻硬币 Python

[蓝桥杯 2013 省 B] 翻硬币 题目背景 小明正在玩一个“翻硬币”的游戏。 题目描述 桌上放着排成一排的若干硬币。我们用 * 表示正面&#xff0c;用 o 表示反面&#xff08;是小写字母&#xff0c;不是零&#xff09;&#xff0c;比如可能情形是 **oo***oooo&#xff0c;如果…

【CNN轻量化】ParameterNet: Parameters Are All You Need 参数就是你所需要的

论文链接&#xff1a;http://arxiv.org/abs/2306.14525 代码链接&#xff1a;https://github.com/huawei-noah/Efficient-AI-Backbones 一、摘要 现有的低FLOPs模型&#xff08;轻量化模型&#xff09;无法从大规模预训练中受益。本文旨在增加大规模视觉预训练模型中的参数数量…

UE4_官方动画内容示例1.3_ 运动混合空间(Locomotion BlendSpace)

如何使用运动&#xff08;Locomotion&#xff09;混合空间将Actor在不同方向上及不同速度的运动混合起来。&#xff08;例如&#xff0c;展示了一个混合了以不同速度向后、前、左和右走路/跑步动作的Actor&#xff09;。 一、相关知识点&#xff1a; 混合空间是允许根据多个输…

UniTask 异步任务

文章目录 前言一、UniTask是什么&#xff1f;二、使用步骤三、常用的UniTask API和示例1.编写异步方法2.处理异常3.延迟执行4.等待多个UniTask或者一个UniTas完成5.异步加载资源示例6.手动控制UniTask的完成状态7.UniTask.Lazy延迟任务的创建8.后台线程切换Unity主线程9.不要返…

java数据结构与算法刷题-----LeetCode406. 根据身高重建队列

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 1. 从高到底排序 1. 从高到底排序 解题思路&#xff1a;时间复杂…

MCU技术的创新浪潮与产业变革

MCU技术的创新浪潮与产业变革 一、MCU技术的创新发展 MCU&#xff0c;即微控制器&#xff0c;作为现代电子设备的核心部件&#xff0c;一直在不断地创新与发展。随着科技的进步&#xff0c;MCU的性能得到了极大的提升&#xff0c;功能也越来越丰富。从8位到32位&#xff0c;再…

MYSQL数据库管理基本操作

一、数据库的基本操作 1、登录数据库 [rootmysql-server ~]#mysql -uroot -p123456 ###直接回车&#xff0c;则进入数据库[rootmysql-server ~]#mysql -u root -p ###直接回车 Enter password: ###输入密码 方法一&#xff1a…