
◆ 博主名称: 小此方-CSDN博客 大家好,欢迎来到小此方的博客。
⭐️算法系列个人专栏: 算法实战论
⭐️此方的GitHub: github_此方
⭐️ Re系列专栏:我们思考 (Rethink) · 我们重建 (Rebuild) · 我们记录 (Record)
文章目录
- 概要&序論
- 一、聊聊回文串问题的一般解法有哪几种
- 二、动态规划解决回文串问题的母题
- 三、子题就是在母题的基础上稍微变形
- [四、有时候numsi != numsj的情况也要考量](#四、有时候nums[i] != nums[j]的情况也要考量)
概要&序論
回文串问题一直都很头疼,但是当你学完动态规划的回文串解题思路。我可以保证------90%的回文串问题都会从中等困难题变成简单题。
一、聊聊回文串问题的一般解法有哪几种
中心扩展算法 -> n² / 1
马拉车算法 -> n / n
动态规划 -> n² / n²
中心扩展算法,大概是大家刚开始解除算法的时候采用的一种方法,简单,但是面对复杂问题需要好好动动脑子。马拉车算法,效率高,但是不好学,并且限制比较多。
动态规划算法,虽然时间复杂度和空间复杂度不怎么样,但是他最简单,而且非常的万能。他的根本思想是将所有的字串是否回文的信息,存放在dp表里面。
我们的讲法是:一道母题,全盘照搬------就是这么简单粗暴。
二、动态规划解决回文串问题的母题
状态表示:对于回文串问题,我们之前讲过的"以i位置为结尾"的经验就不可以使用了------你需要知道一个新的经验。
dpij 表示 s 字符串 i, j 的子串,是否是回文串true/false

状态转移方程:

填表顺序:,大方向上来看,是自下而上。

需要初始化吗?显然不需要

代码:
cpp
vector<vector<bool>> dp(s.size(),vector<bool>(s.size(),false));
for(int i = s.size()-1 ; i >= 0 ; i --){
for(int j = i ; j < s.size() ; j ++){
if(s[i] == s[j]){
if(i == j) dp[i][j] = true ;
else if(i +1 == j) dp[i][j] = true;
else dp[i][j] = dp[i+1][j-1];
}
}
}
统计回文串个数
cpp
int count = 0 ;
for(auto e : dp)
for(auto t : e)
if(t == true)
count ++;
return count;
三、子题就是在母题的基础上稍微变形

最长回文子串这题是将计数改为实时统计最长子序列与最长子序列的长度
cpp
vector<vector<int>> dp(s.size(),vector<int>(s.size(),0));
int MaxLen = 0 ;
std::string MaxString = "";
for(int i = s.size()-1 ; i >= 0 ; i --){//i是回文字串的左端点
for(int j = i ; j < s.size() ; j ++){
//j是回文字串的右端点,j必须大于等于i
if(s[i] == s[j]){
if(i == j) dp[i][j] = 1;
else if(i+1 == j) dp[i][j] = 2;
else{
if(dp[i+1][j-1] == 0){
dp[i][j] = 0;
}else{
dp[i][j] = dp[i+1][j-1] + 2;
}
}
}
if(MaxLen < dp[i][j]){
MaxLen = dp[i][j];
MaxString = s.substr(i,j-i+1);
}
}
}
return MaxString;
回文串分割 IV这题是在原基础上做一个遍历分割
cpp
if(s.size() <= 2){
return false;
}
vector<vector<bool>>dp(s.size(),vector<bool>(s.size(),false));
for(int i = s.size()-1 ; i >= 0 ; i --){
for(int j = i ; j < s.size() ; 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];
}
}
}
}
for(int i = 1 ; i < s.size()-1 ; i ++){
for(int j = i ; j < s.size()-1 ; j ++){
//0~i, i~j , j~s.size()-1
//i和j不能和边界重合,因为如果发生,那么就会少一段
//i和j可以重合,应为重合后也算一段
if(dp[0][i-1] && dp[i][j] && dp[j+1][s.size()-1]){
return true;
}
}
}
return false;
cpp
vector<vector<bool>> dp(s.size(),vector<bool>(s.size(),false));
for(int i = s.size()-1 ; i >= 0 ; i --){
for(int j = i ; j < s.size() ; 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> Sdp(s.size(),INT_MAX);//表示以i
//为结尾[0,i]的字串的最少回文字串分割次数
for(int i = 0 ; i < s.size() ; i ++){
if(dp[0][i] == true){
Sdp[i] = 0;
continue;
}
int MinLen = INT_MAX;
for(int j = 0 ; j <= i ; j ++){
//[0 ,j-1],[j,i],我要让后者是一个回文串,然后前者进一步分割
if(dp[j][i] == true && j-1 >= 0){
MinLen = min(MinLen, Sdp[j-1] + 1);
}
}
Sdp[i] = MinLen;
}
return Sdp[s.size()-1];
四、有时候numsi != numsj的情况也要考量
讲一个回文子序列,dpij表示以ij为左右端点的最长回文子序列的长度,如果si == sj 那么我们可以继续考量dpi+1j-1。否则:
去掉左边那个或者右边那个。然后再判断左右是否相等,并进一步判断dpi+1j-1。为什么这么想?因为dpij-1,dpi+1j,已经包含了所有的情况。
cpp
vector<vector<int>> dp(s.size(),vector<int>(s.size(),1));
int MaxLen = 1 ;
for(int i = s.size()-1 ; i >= 0 ; i --){
for(int j = i ; j <= s.size()-1 ; 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][j-1],dp[i+1][j]);
}
MaxLen = max(MaxLen,dp[i][j]);
}
}
return MaxLen;
好的本文内容到此结束,我是此方,我们下期再见。