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

动态规划-详解回文串系列问题

目录

一、最长回文子串(LeetCode 5)

二、回文子串计数(LeetCode 647) 

 三、分割回文串系列

1. 分割回文串 IV(LeetCode 1745) 

2. 分割回文串 II(LeetCode 132)

四、最长回文子序列(LeetCode 516) 

 五、让字符串成为回文串的最少插入次数(LeetCode 1312)

总结 


回文串是字符串算法中的经典主题,涉及“最长回文子串”“回文子串计数”“分割回文串”等多个变种问题。这类问题的核心是利用动态规划挖掘子问题的递推关系,下面逐一解析其解题思路。
 

一、最长回文子串(LeetCode 5)


问题:给定字符串  s ,找出最长的连续回文子串。
 
动态规划思路
 
- 状态定义: dp[i][j]  表示子串  s[i..j]  是否为回文串( true/false )。
- 状态转移:
- 若  s[i] == s[j] ,则  dp[i][j]  由  dp[i+1][j-1]  决定(子串内部是否回文)。
- 边界情况:当  i == j (子串长度为1)时, dp[i][j] = true ;当  j = i+1 (子串长度为2)时, dp[i][j] = (s[i] == s[j]) 。
- 结果记录:遍历过程中记录最长回文串的起始位置  left  和长度  ret 。
 
代码核心逻辑
 

vector<vector<bool>> dp(n, vector<bool>(n, false));
int left = 0, ret = 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] && (j - i + 1) > ret) {ret = j - i + 1;left = i;}}}
}
return s.substr(left, ret);

二、回文子串计数(LeetCode 647) 


问题:统计字符串中所有回文子串的数量。
 
动态规划思路
 
- 状态定义:与“最长回文子串”一致, dp[i][j]  表示  s[i..j]  是否为回文串。
- 状态转移:同上述逻辑,若  s[i] == s[j] ,则  dp[i][j]  由  dp[i+1][j-1]  推导。
- 结果统计:每当  dp[i][j] == true  时,计数器加一。
 
代码核心逻辑
 

vector<vector<bool>> dp(n, vector<bool>(n, false));
int ret = 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]) ret++;}}
}
return ret;


 三、分割回文串系列

这类问题的关键是先预处理所有子串是否为回文,再结合具体分割要求推导。
 

1. 分割回文串 IV(LeetCode 1745) 


问题:判断字符串能否分割成三个非空回文子串。
 
解题步骤
 
- 步骤1:预处理回文子串:用动态规划得到  dp[i][j] ( s[i..j]  是否为回文)。
- 步骤2:枚举分割点:枚举第二个回文子串的起止位置  i  和  j ,验证三个分段  s[0..i-1] 、 s[i..j] 、 s[j+1..n-1]  是否均为回文。
 
代码核心逻辑


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]) {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;

2. 分割回文串 II(LeetCode 132)


问题:求将字符串分割为全回文子串的最少分割次数。
 
解题步骤
 
- 步骤1:预处理回文子串:得到  isPal[i][j] ( s[i..j]  是否为回文)。
- 步骤2:动态规划求最少分割次数:定义  dp[i]  为  s[0..i]  的最少分割次数。
- 若  s[0..i]  本身是回文,则  dp[i] = 0 。
- 否则,枚举分割点  j ,若  s[j..i]  是回文,则  dp[i] = min(dp[i], dp[j-1] + 1) 。
 
代码核心逻辑
 

vector<vector<bool>> isPal(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]) {isPal[i][j] = (i + 1 < j) ? isPal[i+1][j-1] : true;}}
}
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];

四、最长回文子序列(LeetCode 516) 


问题:找出最长的回文子序列(子序列不要求连续)。
 
动态规划思路
 
- 状态定义: dp[i][j]  表示  s[i..j]  中最长回文子序列的长度。
- 状态转移:
- 若  s[i] == s[j] ,则  dp[i][j] = dp[i+1][j-1] + 2 (两端字符加入子序列)。
- 否则, dp[i][j] = max(dp[i+1][j], dp[i][j-1]) (取子问题的最大值)。
- 边界条件: dp[i][i] = 1 (单个字符的子序列长度为1)。
 
代码核心逻辑

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 + 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];


 五、让字符串成为回文串的最少插入次数(LeetCode 1312)


问题:通过插入字符使字符串成为回文,求最少插入次数。
 
动态规划思路
 
- 状态定义: dp[i][j]  表示将  s[i..j]  变为回文的最少插入次数。
- 状态转移:
- 若  s[i] == s[j] ,则  dp[i][j] = dp[i+1][j-1] (无需插入,继承子问题结果)。
- 否则, dp[i][j] = min(dp[i+1][j] + 1, dp[i][j-1] + 1) (在左端或右端插入字符,取次数较少的方案)。
 
代码核心逻辑
 

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

总结 


回文串系列问题的核心是动态规划的状态定义与转移,其中“预处理所有子串是否为回文”是多个问题的通用步骤。掌握这一核心思路后,可灵活应对“最长、计数、分割、子序列、插入次数”等不同变种,举一反三地解决字符串中的回文类问题。


文章转载自:

http://ZiC0tHvk.khpgd.cn
http://Ho3QEBBc.khpgd.cn
http://0lOghb2Q.khpgd.cn
http://7EPos3mY.khpgd.cn
http://WFHsYkKp.khpgd.cn
http://GCFYpyV7.khpgd.cn
http://Idrt10qW.khpgd.cn
http://5b77otwi.khpgd.cn
http://WUpmQ50P.khpgd.cn
http://ibBz1T6Z.khpgd.cn
http://L4smJpS9.khpgd.cn
http://IjAJLiFC.khpgd.cn
http://6z3ChYA3.khpgd.cn
http://hfihHmdn.khpgd.cn
http://jW8Xxj6u.khpgd.cn
http://D7jbjkGU.khpgd.cn
http://9ukWDxL8.khpgd.cn
http://W20vkl49.khpgd.cn
http://6CKjV7TQ.khpgd.cn
http://7TeSPZXX.khpgd.cn
http://OeEiKtiZ.khpgd.cn
http://39jClSNh.khpgd.cn
http://8bgHBL1N.khpgd.cn
http://N7fwFk06.khpgd.cn
http://F0AQmPIr.khpgd.cn
http://GBh2XrCY.khpgd.cn
http://gEjjXZF7.khpgd.cn
http://bkR9QqRY.khpgd.cn
http://ITAe8g3q.khpgd.cn
http://NHCsLNrz.khpgd.cn
http://www.dtcms.com/a/387625.html

相关文章:

  • C语言基础学习(五)——进制
  • 如何在C#中将 Excel 文件(XLS/XLSX)转换为 PDF
  • 【Error】django-debug-toolbar不显示:Failed to load module script
  • Windows 版本 WDK 版本 Windows SDK Visual Studio各版本对应关系
  • WPF 快速布局技巧
  • K8S YAML 功能详解:让容器配置更灵活
  • CAD迷你看图下载安装教程(2025最新版)
  • 根据文本区域`textarea`的内容调整大小`field-sizing:content`
  • avcodec_send_packet闪退问题
  • ftrace的trace_marker使用
  • ★基于FPGA的通信基础链路开发项目汇集目录
  • SpringBoot中@Value注入失败问题解决
  • DotCore进程CPU飙高跟踪处理方案
  • PantherX2黑豹X2 armbian 编译rkmpp ffmpeg 实现CPU视频转码
  • 2、Logstash与FileBeat详解以及ELK整合详解(Logstash安装及简单实战使用)
  • ENVI系列教程(六)——自动采集控制点的 RPC 正射校正
  • 多可见光线索引导的热红外无人机图像超分辨率重建
  • CE-RED 是什么?
  • Win10上VScode 进行ssh登录服务器时免密登录
  • AWS Global Accelerator 详解:比传统 CDN 更快的全球加速方案
  • Apollo学习之预测模块二
  • Ubuntu安装qbittorrent-nox并启用远程访问webui
  • Qt QLegend详解
  • C++ 初识
  • 从零实现 Qiankun 微前端:基座应用控制子应用路由与信息交互
  • 云函数(Serverless)深度解读
  • 设计模式概述
  • 基于 TCP 协议的 C++ 计算器项目实现:从网络通信到协议封装
  • 【分布式技术】深入理解AMQP(高级消息队列协议)
  • 海外短剧分销系统开发:技术栈选型与核心模块实现指南