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

【动态规划 | 回文字串问题】动态规划解回文问题的核心套路

在这里插入图片描述

算法相关知识点可以通过点击以下链接进行学习一起加油!
斐波那契数列模型路径问题 多状态问题子数组子序列

回文问题在面试中屡见不鲜,动态规划能高效解决这类问题。本文将直击核心,详解如何用DP快速判断子串是否回文:从状态定义到转移方程,再到遍历技巧,助你掌握解题精髓,轻松应对最长回文子串、回文分割等高频考题!

请添加图片描述

Alt

🌈个人主页:是店小二呀
🌈C/C++专栏:C语言\ C++
🌈初/高阶数据结构专栏: 初阶数据结构\ 高阶数据结构
🌈Linux专栏: Linux
🌈算法专栏:算法
🌈Mysql专栏:Mysql

🌈你可知:无人扶我青云志 我自踏雪至山巅 请添加图片描述

文章目录

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

647. 回文子串

题目】:647. 回文子串

在这里插入图片描述

算法思路

在这里插入图片描述

在回文串相关问题中,通常有三种常见的解决方法:中心扩展算法、马拉车算法和动态规划算法。在本系列中,我们采用动态规划来解决“回文数”这一类问题。

通过动态规划,可以有效地将原本较难的“回文数”问题转化为较简单的问题。关键在于"将所有子串是否为回文的信息保存在一个 dp 表中"。

这里不能简单地依赖“经验 + 题目要求”来直接定义状态表示,因为无法仅通过前部分和前一个元素的回文状态来推导后续状态。对于回文串问题,更合适的方法是从两侧入手,通过状态表示来建立状态转移方程,逐步判断子串是否为回文

在动态规划中,dp[i][j] 表示字符串 s[i, j] 是否为回文串。我们需要分析两种情况:一是两侧字符相等,二是两侧字符不等。如果两侧字符相等,则可以进一步细分为不同的状态进行分析。

关于初始化,通过矩阵图和状态转移方程的分析,我们可以得出状态转移是从下往上的。

这样,动态规划通过构建 dp 表,能够从小规模的子问题逐步解决整个问题,最终判断整个字符串是否为回文串。同时,i <= j 的条件避免了出现 j > i 的情况,因此无需额外的初始化操作。

通过矩阵分析,每个表格对应一个区间,表示不同区间的子串。动态规划优化了暴力搜索的过程,通过逐步计算并记录子串的回文信息,避免了重复计算

代码实现

class Solution {
public:int countSubstrings(string s) {//1.创建dp表int n = s.size();vector<vector<bool>> dp(n, vector<bool>(n));//2.填表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] == true) ret++;}}//3.返回值return ret;     }
};

5. 最长回文子串

题目】:5. 最长回文子串

在这里插入图片描述

算法思路

在这里插入图片描述

首先,我们需要判断子串是否是回文。这时,我们可以利用‘通过 dp 表统计所有子串是否为回文’的技巧,从 dp 表的起始位置获取我们需要的结果。

根据‘经验 + 题目要求’,状态表示为 s[i, j] 是否为回文串。如果需要返回最长回文串,可以引入变量 beginlen 来进行记录和判断。初始化时,依据矩阵分析和状态转移方程,状态填表应从下往上进行。

s[i] == s[j] 时,可以通过分析三种情况来更新 dp[i][j],具体为:dp[i][j] = (i + 1 < j) ? dp[i + 1][j - 1] : true;

代码实现

class Solution {
public:string longestPalindrome(string s) {//1.创建dp表int n = s.size();vector<vector<bool>> dp(n, vector<bool>(n));//2.填表操作int len = 0;int begin = 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 && len < j - i + 1){len = j - i + 1;begin = i;}}}//3.返回值return s.substr(begin, len);}
};

1745. 分割回文串 IV

题目】:1745. 分割回文串 IV

在这里插入图片描述

算法思路

在这里插入图片描述

还记得那个小技巧——‘通过 dp 表统计所有子串是否为回文’吗?最后,使用两个 for 循环来遍历,确保满足条件 if(dp[0][i - 1] && dp[i][j] && dp[j + 1][n - 1]) return true;。在这里,需要注意 for 循环参数的填写细节,确保正确地遍历所有可能的子串区间。

代码实现

class Solution {
public:bool checkPartitioning(string s) {//1.创建dp表int n = s.size();vector<vector<bool>> dp(n, vector<bool>(n));//2.填表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;}          }//3.全部枚举判断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]) return true;}} return false;}
};

132. 分割回文串 II

题目】:132. 分割回文串 II

在这里插入图片描述

算法思路

在这里插入图片描述

根据经验和题目要求,我们可以得到状态表示。具体来说,可以选择以[i, j]的范围来表示状态,也可以选择以i位置为结尾来表示状态。由于问题要求最小分割次数,因此更适合选择以i位置为结尾的方式来进行状态表示

通过绘图分析,我们可以聚焦于i位置的情况。只要i位置满足最长回文子串,则分割次数为0。需要结合之前的状态来进行分析,得到状态转移方程。如果[0, i]不是回文串,则需要进行前置分割;当[j, i]是回文串时,我们将状态传递给[0, j],并根据[0, j]是否为回文串来确定状态转移,从而形成完整的状态转移方程。这里为了不影响max函数,位置默认初始化为最大值。

判断回文串时需要不断遍历,时间复杂度较高。为了解决这个问题,我们可以将回文串的信息存储在dp表中,从而优化代码的效率。

代码实现

class Solution {
public:int minCut(string s) {//1.是否回文串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++)if(s[i] == s[j]) isPal[i][j] = i+ 1 < j ? isPal[i + 1][j - 1] : true;//2.创建dp表vector<int> dp(n, INT_MAX);//3.填表操作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[j - 1] + 1, dp[i]);}}//4.返回值return dp[n - 1];}
};

516. 最长回文子序列

题目】:516. 最长回文子序列

在这里插入图片描述

算法思路

在这里插入图片描述

一开始考虑根据‘经验 + 题目要求’,以i位置元素为结尾来得到状态表示。但问题在于,若i位置之前是回文串,加入i位置元素后,是否依然是回文串并不确定。因此,无法直接得到状态转移方程。

如果这种‘经验’不可行,我们可以尝试其他的策略,比如考虑s字符串的[i, j]区间。这也是一种经验,根据当前状态标识,这个区间表示的是最长回文子序列。接下来,我们需要根据最近一次的状态,通过它们之间的关系推导出状态转移方程。根据矩阵分析,我们可以直接合并状态,而无需单独初始化。

代码实现

class Solution {
public:int longestPalindromeSubseq(string s){//1.小技巧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++)if(s[i] == s[j]) isPal[i][j] = i + 1 < j ? isPal[i + 1][j - 1] : true;//2.创建dp表vector<vector<int>> dp(n, vector<int>(n));//3.填表操作for(int i = n - 1; i >= 0; i--){for(int j = i; j < n; j++){if(s[i] != s[j]) dp[i][j] = max(dp[i][j - 1], dp[i + 1][j]);else dp[i][j] = j == i ? 1 : dp[i + 1][j - 1] +2;}}//4.返回值return dp[0][n - 1];}
};

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

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

在这里插入图片描述

算法思路

在这里插入图片描述

根据‘经验 + 题目要求’,我们需要确定状态表示,即在s的[i, j]区间内,使其成为回文串所需的最小插入次数。这里可以通过绘图分析具体情况。对于s[i] == s[j],可以分为两种情况:如果相等,则无需插入,直接向最近一次状态转移;如果不相等,则需要进一步分析,可以选择i前进一步或j后退一步进行递归处理。

在填表时,我们需要从下往上遍历每一行,并在每一行中从左往右更新状态,无需额外初始化。

代码实现

class Solution {
public:int minInsertions(string s) {//1.创建dp表int n = s.size();vector<vector<int>> dp(n, vector<int>(n));//2.填表操作for(int i = n - 1; i >= 0; i--){for(int j = i + 1; j < n; j++)//i == j一定相等。{if(s[i] == s[j]) dp[i][j] =dp[i + 1][j - 1];else dp[i][j] = min(dp[i][j - 1], dp[i + 1][j]) + 1;}}//3.返回值return dp[0][n - 1];}
};

在这里插入图片描述
快和小二一起踏上精彩的算法之旅!关注我,我们将一起破解算法奥秘,探索更多实用且有趣的知识,开启属于你的编程冒险!

http://www.lryc.cn/news/610069.html

相关文章:

  • 打卡day28
  • Memcached缓存与Redis缓存的区别、优缺点和适用场景
  • Java 大视界 -- Java 大数据在智能交通智能停车诱导与车位共享优化中的应用(381)
  • 【C#】操作Execl和Word文件-1
  • orchestrator部署
  • 11.Linux 权限管理,控制对文件的访问(ACL)
  • git操作命令和golang编译脚本
  • 【Spring】SpringBoot 自动配置,@ComponentScan、@Import、ImportSelector接口
  • 【QT】安装与配置
  • 计量学基础 - (二)计量单位制
  • NX982NX984美光固态闪存NX992NY102
  • 高速信号设计之 PCIe6.0 篇
  • Linux之Shell脚本快速入门
  • 【2025最新】Spring Boot + Spring AI 玩转智能应用开发
  • 微服务的编程测评系统10-竞赛删除发布-用户管理-登录注册
  • 雷达系统工程学习:自制极化合成孔径雷达无人机
  • Flask全栈入门:打造区块链艺术品交易所
  • Oracle 定时任务相关
  • Tomcat虚拟主机配置详解和多实例部署
  • k8s的毫核
  • 太阳光模拟器塑料瓶暴晒试验
  • Vue2实现docx,xlsx,pptx预览
  • P1002 [NOIP 2002 普及组] 过河卒
  • ubuntu22.04系统实践 linux基础入门命令(三) 用户管理命令
  • SpringMVC实战指南:从环境搭建到功能实现全解析
  • 先知模型或者说从容的模型
  • RTOS如何保证实时性
  • React 入门:环境搭建、JSX、组件、事件与状态管理
  • 云原生攻防6(Kubernetes扩展知识)
  • 前端开发(HTML,CSS,VUE,JS)从入门到精通!第五天(jQuery函数库)