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

[dp14_回文串] 分割回文串 II | 最长回文子序列 | 让字符串成为回文串的最少插入次数

目录

1.分割回文串 II

题解

2.最长回文子序列

题解

3.让字符串成为回文串的最少插入次数

题解


回文串,想通过s[i] == s[j] 来实现状态变化,由二维数组 右下角 开始扩散

 

1.分割回文串 II

链接: 132. 分割回文串 II

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

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

示例 1:

输入:s = "aab"
输出:1
解释:只需一次分割就可将 s 分割成 ["aa","b"] 这样两个回文子串。

示例 2:

输入:s = "a"
输出:0

示例 3:

输入:s = "ab"
输出:1

  • 在做这道题之前,可以去做一下 单词拆分那题,判断 j--i 是不是即可,是的话 dp[j-1]+1

题解

1.状态表示

这道题和 “单词拆分” 类似,需要从左往右一个一个试,所以我们可以根据经验 + 题目要求 来找出状态表示。

  • 之前的题用某个位置为结尾的状态表示,推不出状态转移方程。。经验失效了,所以用的方法解决。这道题可以。
  • dp[i] 表示: s [0,i] 区间上的最长的子串,最少分割次数

2.状态转移方程

  • 仅需考虑[0,i]区间的子串,不需要考虑后面的。

如果[0,i]区间的子串本身就是回文了,根本不需要切割了。

  • 如果[0,i]区间的子串不是回文,这个时候就想dp[i]能不能用之前的状态来表示一下,如果可以就能推出状态转移方程。
  • 如果 [0,i] 区间 来一个 j 切出来一个 [j,i] 的子串,如果[j,i] 是一个回文串,接下来在 [0,j-1] 看看切多少刀,然后再加上切出来的[j,i] 这一刀就可以了。

  • 我们要经常判断 0 ~ i,j ~ i 是否是回文,总体时间复杂度O(N^3)。

所以优化一下:

  • 回文子串,二维 dp 表,将所有的子串是否是回文的信息,保存在 dp 表里面
  • 这样就可以以O(1)时间复杂度在 dp 表中判断是否是回文了,时间复杂度降到O(N^2)

3.初始化

  • 这道题是不用初始化的,因为只有 j == 0,dp[j -1]才会越界,但是 j > 0。
  • 因为只有本身的话,没什么好切割的
  • 但是因为dp[i] 要找最小,如果不初始化 dp表里面都是0,选最小会有影响
  • 所以 dp 表内所有的值都初始化为无穷大。

4.填表顺序

  • 从左往右

5.返回值

  • dp[i] 表示: s [0,i] 区间上的最长的子串,最少分割次数
  • 这道题要求整个区间的最少分割次数,所以返回 dp[n-1]

找回文串的思路上一篇文章当中有讲到过

class Solution {
public:int minCut(string s) {int n=s.size();if(n==1) return 0;//先 处理 回文串vector<vector<bool>> dp(n,vector<bool>(n,false));for(int i=n-1;i>=0;i--){for(int j=i;j<n;j++) //!!!!向后移动{if(s[i]==s[j]){if(i==j)dp[i][j]=true;else if(i+1==j)dp[i][j]=true;elsedp[i][j]=dp[i+1][j-1];}}}
//双二维dp 先判bool
//再 切割次数//二维数组 表切割起始 和结束vector<int> ret(n,INT_MAX);//求最小值 初始化为 最大ret[0]=0;for(int i=1;i<n;i++){if(dp[0][i]) ret[i]=0;for(int j=1;j<=i;j++){if(dp[j][i]){ret[i]=min(ret[i],ret[j-1]+1);}}}return ret[n-1];}
};

2.最长回文子序列

链接:516. 最长回文子序列

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

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

示例 1:

输入:s = "bbbab"
输出:4
解释:一个可能的最长回文子序列为 "bbbb" 。

示例 2:

输入:s = "cbbd"
输出:2
解释:一个可能的最长回文子序列为 "bb" 。

如果是 连续的子数组,我们可以很简单的想到

class Solution {
public:int longestPalindromeSubseq(string s) {//bool 判回文int n=s.size(),ret=0;vector<vector<bool>> dp(n,vector<bool>(n,false));for(int i=n-1;i>=0;i--){for(int j=i;j<n;j++){if(s[i]==s[j]){if(i==j)dp[i][j]=true;else if(i+1==j)dp[i][j]=true;elsedp[i][j]=dp[i+1][j-1];}if(dp[i][j]) ret=max(ret,j-i+1);}// 左下往右上填}return ret;}
};

那么子序列呢

题解

1.状态表示

  • 关于子序列的题我们做了很多了,前面都是以 i 位置为结尾 + 题目要求分析问题
    这里我们先根据之前的经验来一个状态表示

dp[i] 表示:以 i 位置元素为结尾的所有子序列中,最长回文子序列的长度。

  • 以 i 位置元素为结尾,势必会用到 i 位置之前,i - 1, i - 2,i - 3。。。这种找子序列的套路可以跟在它们任意后面,根据 dp[i -1],dp[i - 2],dp[i - 3] 去填 dp[i]。
  • 但是这里有个问题上面状态表示只知道前面最长回文子序列的长度
  • 不知道回文子序列是什么,加上 i 位置是否构成回文子序列。因此上面状态表示不对。

在回文子串哪里,如果以 i 位置为起点,j 位置为结束的子串是一个回文子串,i 前面加一个字符,j 后面加一个字符,如果相等,依旧是一个回文子串。

  • 比如在s 字符串里面依旧选一个 i -> j 区间,如果知道这个区间内的最长回文子序列的长度,如果 i 前面字符 和 j 后面字符是一样的。
  • 那也能推出来 i - 1 -> j +1 区间回文子序列的长度。在原有基础上多加一个2。因此状态表示

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

  • 还是应用到了一维不行,那就在加一维表区间的思想

2.状态转移方程

如果 s[i] == s[j]

  • i == j ,最长的回文子序列的长度为1
    i +1 == j ,最长的回文子序列的长度为2
    i + 1 < j ,现在 i + 1 -> j - 1 区间内找最长的,然后在加上 i j,最长的回文子序列的长度为dp[i + 1][j -1] + 2


如果s[i] != s[j]

  • i j 一定不可能同时存在构不成回文子序列,那就去找 i + 1 -> j 区间 和 i -> j - 1区间 找找,然后取两个区间的最大值就可以了。
  • 注意 i + 1 - > j - 1区间已经包括在上面的区间了,因此不用单独去找了。

3.初始化

因为 s[i] == s[j]情况分的特别细 ,所有dp[i+1][j-1]是不会越界的。

  • 考虑一下 s[i] != s[j],来一个二维dp表,我们只会用到上三角,因为要保证 i <= j。
  • 只有 i == j 并且 第一个位置 和 最后一个位置 dp[i][j-1] 和 dp[i+1][j] 会越界
  • 但是注意 是在 i == j 的情况。因此我们可以在填表时提前特殊处理一下 i == j dp[i][j] =1。
  • 所以 dp[i][j-1] 和 dp[i+1][j]这两个位置越界根本不会发生,因此表无需初始化。

4.填表顺序

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

5.返回值

  • dp[i][j] 表示:s 字符串 [i, j] 区间内的所有子序列,最长的回文子序列的长度。
  • 但是我们要的是整个区间的最长的回文子序列的长度,因此返回 dp[0][n-1]。
class Solution {
public:int longestPalindromeSubseq(string s) {int n=s.size();vector<vector<int>> dp(n,vector<int>(n,0));//初始化for(int i=n-1;i>=0;i--){for(int j=i;j<n;j++){if(s[i]==s[j]){if(i==j)dp[i][j]=1;else if(i+1==j)dp[i][j]=2;elsedp[i][j]=dp[i+1][j-1]+2;}elsedp[i][j]=max(dp[i][j-1],dp[i+1][j]);}}return dp[0][n-1];  }
};
  • 相当于 是从 最右下角开始填
  • s[i] 和 s[j] 的关系,为状态变化契机
  • 要记得初始化DP,才能通过下标来引用

3.让字符串成为回文串的最少插入次数

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

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

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

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

示例 1:

输入:s = "zzazz"
输出:0
解释:字符串 "zzazz" 已经是回文串了,所以不需要做任何插入操作。

示例 2:

输入:s = "mbadm"
输出:2
解释:字符串可变为 "mbdadbm" 或者 "mdbabdm" 。

示例 3:

输入:s = "leetcode"
输出:5
解释:插入 5 个字符后字符串变为 "leetcodocteel" 。

题解

1.状态表示

  • 我们已经做过很多回文串的问题,因此我们还是在这个 s 字符串中 选取一段区间
    i ->j 研究问题 (i <= j)
  • dp[i][j] 表示:s里面 [i,j] 区间内的子串,使它成为回文串的最小插入次数

2.状态转移方程

  • 关于回文串这里从i -> j,我们就以这两个端点来分析问题

当s[i] == s[j]

  • i == j,本身 --不用插入
    i + 1 == j, 相邻--不用插入操作
    i + 1 < j, i 和 j 中间有其他字符,因为 s[i] 已经等于 s[j],所以只用考虑 i + 1 -> j -1区间最小插入次数,正好就是 dp[i + 1][j - 1]

当s[i] != s[j]

  • i 和 j 位置字符不相等,我想要让这个区间是回文串,必须得先让两个端点是回文串,有两个方法
  • 第一种方法,可以考虑在 i 前面加一个 s[j] 然后就可以和 j 位置字符匹配, 然后让 i -> j -1区间成为回文串就行了,就是dp[i][j-1]。
  • 第二种方法,可以考虑在 j 后面加一个 s[i] 然后就可以和 i 位置字符匹配, 然后让 i + 1-> j 区间成为回文串就行了,就是dp[i + 1][j]。

然后取这两种情况的最小值。

3.初始化

  • 当 s[i] == s[j] 情况,分析一下dp[i + 1][j - 1] 会不会越界。
  • 注意 i <= j 只填上三角,当 i == j 的时候对角第一个位置和最后一个位置会越界,但是 i == j 我们已经特殊处理了,i == j 等于 0。因此不用初始化。

还有一点 i + 1 == j ,可以和 i + 1 < j 合并,当 i + 1 == j 可以用dp[i + 1][j - 1] 填。如果在创建 dp 表 初始化就是0,i + 1 == j 放在i + 1 < j里面填,dp[i + 1][j - 1]也是0


当 s[i] != s[j]

  • 肯定不会对角线,因为对角线肯定是 s[i] == s[j]。肯定是不会越界的。

4.填表顺序

  • 当填 i j 发现会用到左边,左下,下边。因此从下往上每一行,每一行从左往右
  • 最右下角 开始填

5.返回值

  • dp[i][j] 表示:s里面 [i,j] 区间内的子串,使它成为回文串的最小插入次数
  • 而我们要的是整个区间的最小插入次数,因此返回 dp[0][n-1]
class Solution {
public:int minInsertions(string s) {int n=s.size();vector<vector<int>> dp(n,vector<int>(n,0));for(int i=n-1;i>=0;i--){for(int j=i;j<n;j++){if(s[i]==s[j]){if(i==j)dp[i][j]=0;else if(i+1==j)dp[i][j]=0;else    dp[i][j]=dp[i+1][j-1];}else{dp[i][j]=min(dp[i][j-1],dp[i+1][j])+1;}}}return dp[0][n-1]; }
};

相关文章:

  • 【JavaEE】Spring AOP的注解实现
  • Java大模型MCP服务端开发-数据库查询(智能问数)
  • 基于PLC的停车场车位控制系统的设计
  • Ubuntu 安装 NVIDIA显卡驱动、CUDA 以及 CuDNN工具
  • [ElasticSearch]Suggest查询建议(自动补全纠错)
  • 鸿蒙NEXT开发文件预览工具类(ArkTs)
  • IT运维常用的软件工具有哪些
  • iOS内存管理中的强引用问题
  • BGP(边界网关协议)
  • STM32单片机入门学习——第39节: [11-4] SPI通信外设
  • G代码中表达式赋值与变量的使用
  • ADI的BF609双核DSP怎么做开发,我来说一说(二)DDR驱动测试
  • 记录一个坑关于STM32 ARM Compiler Version
  • HarmonyOS学习 实验九:@State和@Prop装饰器的使用方法
  • (三)谷歌Code as Policies复现(操作记录)
  • [学习] C语言数据结构深度解析:八种树结构与应用场景详解(代码示例)
  • 【MySQL学习】存储过程
  • 学习笔记十四——一文看懂 Rust 迭代器
  • SIMULIA-Abaqus有限元分析软件针对汽车行业的解决方案
  • 通信算法之266: 无人机信号带宽计算
  • 东莞电商页面设计公司/重庆旅游seo整站优化
  • 南京鼓楼做网站/宁波seo入门教程
  • 富锦网站/比优化更好的词是
  • 用ps做网站切片/发布
  • 网站开发与设计期末考试/公司员工培训方案
  • 个人网站做哪些内容/宁德市蕉城区