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

回文串问题

文章目录

  • 647. 回文子串
  • 5. 最长回文子串
  • 1745. 分割回文串 IV
  • 132. 分割回文串 II
  • 516. 最长回文子序列
  • 1312. 让字符串成为回文串的最少插入次数
  • 总结

647. 回文子串

题目链接
在这里插入图片描述
状态表示:
回文子串的核心是 “某个区间的字符串是否对称”,比如判断 aaa 是不是回文,需要知道这个区间的两端和中间的情况。

dp[i][j] 定义为:字符串中从第 i 个字符到第 j 个字符的子串([i,j])是否是回文串(true 是,false 否)。这样设计的原因:回文是 “区间属性”,必须用两个下标(起点 i 和终点 j)才能定位一个子串,二维数组刚好能记录所有可能的区间状态。

状态转移方程:

  • 第一步:看两端字符如果 s[i-1] != s[j-1](代码里 i/j 从 1 开始,对应原串下标 i-1/j-1),说明两端不对称,直接 dp[i][j] = false。
  • 第二步:两端相等时,看子串长度若 s[i-1] == s[j-1],再分 3 种情况:
  • 子串长度为 1(i == j):单个字符本身就是回文(比如 s[2])→ dp[i][j] = true。
  • 子串长度为 2(i+1 == j):两个相同字符是回文(比如 ss)→ dp[i][j] = true。
  • 子串更长(长度 > 2):需要中间的子串 [i+1, j-1] 也是回文(比如 abcba,中间 bcb 是回文)→ dp[i][j] = dp[i+1][j-1]。

总体思路就是把字符串的所有的子串是否回文都统计到一个二维dp表里。然后根据需求返回结果即可。
填表顺序,因为一开始在没分析前是不知道的,可以常规上-》下,左-》右遍历。后面状态转移的时候,推出dp[i][j]依赖哪一项,再根据那一项的位置去修改填表顺序即可。
比如这里dp[i][j]由dp[i+1][j-1]推导而来。(i+1,j-1)在(i,j)的左下角,要确保这个值先存在才能推导出(i,j)。所以就是下-》上,左-》右。

class Solution {
public:int countSubstrings(string s) {//dp[i][j]:(i,j)这个子串是否是回文串。也就是用一个二维dp去统计所有的子串是否回文的信息int n=s.size();vector<vector<bool>>dp(n+1,vector<bool>(n+1,false));//初始化:(0,0)空串回文dp[0][0]=1;//返回结果是数目,统计后符合条件的累加即可int res=0;for(int i=n;i>=1;i--)for(int j=i;j<=n;j++){//s[i]!=s[j]if(s[i-1]!=s[j-1])dp[i][j]=false;else //s[i]==s[j],然后分类讨论即可{if(i==j||i+1==j)dp[i][j]=true;else dp[i][j]=dp[i+1][j-1];}if(dp[i][j])res++;}return res;}
};

5. 最长回文子串

题目链接

在这里插入图片描述
根据上一题,我们知道对付回文串可以把它所有的子串是否回文 统计到一个二维dp表中。这题让求最长的回文子串是什么。我们只需要在统计好的dp表里找到对应要求的子串的下标即可。

class Solution {
public:string longestPalindrome(string s) {int n=s.size();int reti=0,retj=0;int 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||i+1==j)dp[i][j]=true;else dp[i][j]=dp[i+1][j-1];}//找到最长回文子串的首尾下标if(dp[i][j]==true){
if(ret<(j-i))
{ret=j-i;reti=i;retj=j;
}}}return s.substr(reti,(retj-reti+1));}
};

1745. 分割回文串 IV

题目链接

在这里插入图片描述
这题一样的只是返回值的需求不同。我们先统计出所有子串的回文信息。
然后分析它的需求,让我们判断当前字符串能否分隔成3个非空的回文子串。 只需要一个遍历就行了,通过i ,j把字符串分隔成3个区间,(0,i)(i+1,j) (j+1,n) (j>=i) 然后判断三个区间都能回文即可

class Solution {
public:bool checkPartitioning(string s) {int n=s.size();//统计每个子串的回文信息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||i+1==j)dp[i][j]=true;elsedp[i][j]=dp[i+1][j-1];}}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

题目链接
在这里插入图片描述
先统计字符串的子串的回文信息。
然后再用一个dp表去统计最少的分隔次数。

状态表示:
dp[i]表示,(0,i)这个字符串,分割成回文串的最少分割次数。

状态转移方程:
然后分类讨论,(0,i)是回文串 --》dp[i]=0。 不用分割。
(0,i)不是回文串,我们就要去分割这个字符串让它的子串回文。可以引入j(j<=i)去分割,当(j,i)回文的时候,就只需判断(0,j-1)是否回文,不回文就继续分割。所以dp[i]=dp[j-1]+1。

判断一个区间是否回文就在我们前面统计所有子串是否回文的dp表里找即可

class Solution {
public:int minCut(string s) {int n=s.size();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||i+1==j)dp[i][j]=true;else dp[i][j]=dp[i+1][j-1];}vector<int>dp2(n,INT_MAX);for(int i=0;i<n;i++){if(dp[0][i])dp2[i]=0;else{for(int j=1;j<=i;j++){if(dp[j][i])dp2[i]=min(dp2[j-1]+1,dp2[i]);}}}return dp2[n-1];}
};

516. 最长回文子序列

题目链接

在这里插入图片描述
首先要弄清楚子序列 和 子串的概率。子串必须连续,子序列不需要连续。

  1. 状态表示
    dp[i][j]:字符串区间 [i, j] 内的最长回文子序列长度(子序列不要求连续,只要求顺序)。

  2. 状态转移方程
    根据区间两端字符 s[i] 和 s[j] 是否相等,分两种情况:

  • 若 s[i] == s[j]:两端字符可加入回文子序列,长度 = 中间区间 [i+1, j-1] 的最长回文子序列长度 + 2 → dp[i][j] = dp[i+1][j-1] + 2。
  • 若 s[i] != s[j]:最长回文子序列来自 “去掉左端 i” 或 “去掉右端 j” 的子区间,取最大值 → dp[i][j] = max(dp[i+1][j], dp[i][j-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--){//dp[i][i]=1;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;else 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];}
};

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

题目链接

在这里插入图片描述
状态定义
dp[i][j]:子串 s[i…j] 变为回文的最少插入次数。
转移方程

  • 若 s[i] == s[j]:
  • 单字符 / 两相同字符:dp[i][j] = 0(本身是回文)。
  • 更长子串:dp[i][j] = dp[i+1][j-1](两端已匹配,缩小区间)。
  • 若 s[i] != s[j]:需插入字符匹配两端,取 “左 / 右区间插入后 + 1” 的最小值 → dp[i][j] = min(dp[i+1][j], dp[i][j-1]) + 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+1<j)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];}
};

总结

  1. 核心思路
    回文的本质是 “区间两端的对称性”,因此几乎都用二维 DP表示区间 [i,j] 的状态 (如是否回文、最长长度、最少操作数等)。
  2. 子串 vs 子序列
  • 子串:要求连续(如 “最长回文子串”)。
  • 子序列:不要求连续(如 “最长回文子序列”)。二者的状态转移逻辑因 “连续性” 差异略有不同,但都依赖两端字符的比较。
  1. 通用步骤
    1.预处理回文信息:用二维 DP 表(如 dp[i][j] 表示 [i,j] 是否为回文),通过 “两端字符是否相等 + 中间区间状态” 推导。
    2.基于预处理解决问题:如统计回文子串数量、找最长回文、计算最少分割 / 插入次数等,只需在预处理的 DP 表上进一步分析。
  2. 状态转移共性
    无论具体问题,核心逻辑都围绕区间两端 s[i] 和 s[j]:
  • 若 s[i] == s[j]:依赖中间区间 [i+1,j-1] 的状态(子串需中间也连续回文,子序列则需中间最长回文长度)。
  • 若 s[i] != s[j]:子串直接非回文;

简言之:回文问题靠 “区间两端对比 + 二维 DP 统计区间状态”,再结合具体需求(子串 / 子序列、计数 / 最值)推导结果。

http://www.dtcms.com/a/490570.html

相关文章:

  • 【Winform】Gerber文件解析坐标工具代码
  • k8s 网络策略详解--图文篇
  • 伟淼科技深度解析过度营销—告别流量轰炸企业破解营销困局新路径
  • POM思想的理解与示例
  • 案例精选 | 某大型船舶制造集团安全运营服务实践
  • 复习总结最终版:C语言
  • react 无限画布难点和实现
  • 网站开发浏览器wordpress投票类主题
  • Qt_day2
  • DMXAPI |使用1个Key接入主流大模型
  • 三星企业网站建设ppt网站建设需要使用哪些设备
  • 23种设计模式——中介者模式 (Mediator Pattern)详解
  • iOS八股文之 RunLoop
  • zibbix
  • Macbook数据恢复 Disk Drill
  • 公司招聘一个网站建设来做推广制作表情包的软件
  • WebSocket实时通信:Socket.io
  • xml方式bean的配置---实例化bean的方式
  • 212. Java 函数式编程风格 - Java 编程风格转换:命令式 vs 函数式(以循环为例)
  • Ubuntu 24.04 修改 ssh 监听端口
  • 1千元以下做网站的公司wordpress sso插件开发
  • Pytorch神经网络工具箱
  • PyTorch DataLoader 高级用法
  • 怎么做一个网站app吗金华网站建设价格
  • 芷江建设局网站石家庄网站建设公司黄页
  • Excel表----VLOOKUP函数实现两表的姓名、身份证号码、银行卡号核对
  • XMLHttpRequest.responseType:前端获取后端数据的一把“格式钥匙”
  • office便捷办公06:根据相似度去掉excel中的重复行
  • Vue+mockjs+Axios 案例实践
  • http的发展历程