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

[dp12_回文子串] 最长回文子串 | 分割回文串 IV

目录

1.回文子串

题解

2.最长回文子串

题解

3.分割回文串 IV

题解


dp[i][j] 表示 s 字符串 [i, j] 的子串,是否是回文串( 建始末表) 

  • 两个 for 循环的结果,借助二维 dp 来存

1.回文子串

链接:647. 回文子串

给你一个字符串 s ,请你统计并返回这个字符串中 回文子串 的数目。

回文字符串 是正着读和倒过来读一样的字符串。

子字符串 是字符串中的由连续字符组成的一个序列。

示例 1:

输入:s = "abc"
输出:3
解释:三个回文子串: "a", "b", "c"

示例 2:

输入:s = "aaa"
输出:6
解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"

子串和子数组是一样的。

  • 具有不同开始位置或者结束位置的子串
  • 即使是由相同的字符组成,也会被视作不同的子串。

题解

对于这道题相比于动态规划,中心扩展算法和马拉车算法更优秀。

  • 中心扩展算法:时间复杂度O(N^2),空间复杂度O(1)
    马拉车算法:时间复杂度O(N),空间复杂度O(N)
    动态规划:时间复杂度O(N^2), 空间复杂度O(N^2)

动态规划虽然这里不是最优解,但是它的思想可以让有些回文串的困难题变成容易题。

能够将所有的子串是否是回文的信息,保存在 dp 表里面

1.状态表示

既然要把所有子串都放在dp表里面,一个一维dp肯定不够


  • dp[i][j] 表示 s 字符串 [i, j] 的子串,是否是回文串

2.状态转移方程

  • 判断 i - > j 是否是回文串,必须首先 i 位置 和 j 位置字符是相等的,如果都不相等,即使里面在好, i - > j 子串也不回文串

  • i 位置 和 j 位置字符相等,这里分三种情况:
    i == j , i j同一个位置 是回文子串
    i + 1 == j ,i j相邻 是回文子串
    还有就是 i -> j 中间有别的字符,那就看 i + 1 位置 到 j - 1 位置的子串是否是回文子串,而i + 1 位 到 j - 1 是否是回文子串就在dp[i + 1][j - 1]
     

3.初始化

  • 会发生越界的就只有 dp[i + 1][j -1]
  • 但是它会发生越界情况都已经在 i == j,i + 1 < j 这两种情况考虑到了。
  • (以二维表的 视角 来看一下就懂啦)

4.填表顺序

  • dp[i][j] 可能会用到 dp[i + 1][j -1]
  • 所以是从左下往右上填的

5.返回值

  • 统计dp表中true的个数。
class Solution {
public:
    int countSubstrings(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]) 
                dp[i][j]=false;
            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];
                    //不存在越界 因为重合和相邻的情况
                    //已经给这个二维数组 填好了一部分
            }
        }
    }
    int ret=0;
    for(int i=0;i<n;i++)
    {
        for(int j=i;j<n;j++)
        {
            if(dp[i][j])
                ret++;
        }
    }
    return ret;  
    }
};

中心扩展算法

前文:[Lc11_字符串] 最长公共前缀 | 最长回文子串 | 二进制求和

class Solution {
public:
    string longestPalindrome(string s) 
    {
        int n=s.size();
        int len=0;
        string ret;
        for(int i=0;i<n;i++)
        {
            int left=i-1,right=i+1;//ji
            while(left>-1 && right<n &&s[left]==s[right])
                {
                    left--;
                    right++;
                }
            if(right-left-1>ret.size())
    //!!!!(pos,len)   not(pos,end)
                ret=s.substr(left+1,right-left-1);
 
           left=i,right=i+1;//ou
            while(left>-1 && right<n &&s[left]==s[right])
                {
                    left--;
                    right++;
                }
            if(right-left-1>ret.size())
                ret=s.substr(left+1,right-left-1);
        }
        return ret;
    }
};


2.最长回文子串

链接: 5. 最长回文子串

给你一个字符串 s,找到 s 中最长的 回文 子串。

示例 1:

输入:s = "babad"
输出:"bab"
解释:"aba" 同样是符合题意的答案。

示例 2:

输入:s = "cbbd"
输出:"bb"

题解

  • 如果上面那道题搞懂了,这道题完全直接就去做就行了。可以这样做:
  • 判断子串是否是回文 —> 用 dp 表,统计所有子串是否是回文的信息 —> 根据 dp 表的起始和结尾位置得到我们想要的结果。

起始位置 i,回文串结束位置 j,回文串长度 j - 1 + 1

1.状态表示

  • dp[i][j] 表示 s 字符串 [i, j] 的子串,是否是回文串

2.状态转移方程

3.初始化

  • 无需初始化,当用到 dp[i+1][j-1] 是有条件的
  • 当 i + 1 < j才会用,此时不会越界。(确保 是 中间有别人的时候)
  • 只有当 i == j(重合),i + 1 == j(相邻) 才会越界,但是我们已经特殊处理过了。

4.填表顺序

  • dp[i][j] 可能会用到 dp[i + 1][j -1],所以从下往上填。
  • 其实当成子串的坐标来理解 i j 就好啦

5.返回值

  • dp 里面值为 true 的情况下,长度最大的子串的起始位置以及长度
class Solution {
public:
    string longestPalindrome(string s) 
    {
        int n=s.size();
    vector<vector<bool>> dp(n,vector<bool>(n,false));

    for(int i=n-1;i>=0;i--)
//根据DP方向
//用的是i+1,依赖的后一位置
//所以是从后往前填
    {
        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;
                else
                    dp[i][j]=dp[i+1][j-1];
            }
        }
    }
    int len=0,begin=0;
    for(int i=0;i<n;i++)
    {
        for(int j=i;j<n;j++)
        {
            if(dp[i][j])
            {
                int cur=j-i+1;
                if(cur>len)
                {
                    len=cur;
                    begin=i;
                }
            }
        }
    }
    return s.substr(begin,len);   
    }
};


3.分割回文串 IV

链接: 1745. 分割回文串 IV

给你一个字符串 s ,如果可以将它分割成三个 非空 回文子字符串,那么返回 true ,否则返回 false

当一个字符串正着读和反着读是一模一样的,就称其为 回文字符串

示例 1:

输入:s = "abcbdd"
输出:true
解释:"abcbdd" = "a" + "bcb" + "dd",三个子字符串都是回文的。

示例 2:

输入:s = "bcbddxy"
输出:false
解释:s 没办法被分割成 3 个回文子字符串。

给你一个字符串 s,把它分成三个部分,如果这三个部分全都是回文子串,返回true,否则false。

题解

我们先想如何暴力解决这个问题,我们可以把所有的分割出来的三个子串枚举一下,然后判断是否是回文就可以了。

  • 我们可以这样枚举,固定一个 i 位置,一个 j 位置,i j 表示的是分割出来的第二个字符串的起始位置和结束位置
  • 那整个字符串,就被分成 [0, i -1],[i, j], [j+1, n-1] 三个部分。

如果我们能够快速判断这三个子串是否是回文就好了。

  • 看到快速判断,是不是就想到了 我们 dp 表存的起始和结束位置,就可以快速判断

别忘记,回文子串,我们可以用 dp 表保存所有子串是否是回文的信息。

  • 也就是说用动态规划,搞一个二维的dp表,保存所有子串是否是回文的信息
  • 接下来在枚举所有第二个字符串的起始位置和结束位置
  • 仅需在dp表里面查一下三个部分是否是回文就行了。

将三层循环,借助 dp 的状态查询,两层循环就够了

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)
                        dp[i][j]=true;
                    else if(i+1==j)
                        dp[i][j]=true;
                    else
                        dp[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;
    }
};

相关文章:

  • Kotlin作用域函数
  • MyBatis-Plus笔记(下)
  • 龙虎榜——20250414
  • TLS协议四次握手原理详解,密钥套件采用DH密钥交换算法
  • Video Encoder:多模态大模型如何看懂视频
  • 【HFP】蓝牙 HFP 协议状态通知机制研究
  • 数据结构初阶:双向链表
  • 常见的 14 个 HTTP 状态码详解
  • MySQL函数
  • 鸿蒙开发-布局
  • CTF-SQL注入
  • Go:接口
  • 大风频繁,疾风气象大模型竞速:AI如何提前10天预测极端天气?
  • Spark SQL
  • C++ 用红黑树封装map/set
  • PD-1 功能性抗体知多少
  • 静态代码扫描概述
  • 【数据标准】数据标准化-现状分析及评估
  • 信息系统项目管理工程师备考计算类真题讲解二
  • 【补题】Codeforces Round 857 (Div. 1) A. The Very Beautiful Blanket
  • “这是本届政府的态度”,英国明确拒绝与中国脱钩
  • 收藏家尤伦斯辞世,曾是中国当代艺术的推手与收藏者
  • 对话地铁读书人|来自法学副教授的科普:读书日也是版权日
  • “85后”雷海军已任新疆维吾尔自治区统计局局长
  • 复旦大学官宣成立3个创新学院,院士赵东元、副校长周磊和姜育刚分别任院长
  • 海外需求增长推动价格飙升,正丹股份去年净利润暴增119倍