力扣5. 最长回文子串 中心拓展算法+动态规划法

Posted weixin_43739821

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了力扣5. 最长回文子串 中心拓展算法+动态规划法相关的知识,希望对你有一定的参考价值。

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

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

示例 2:

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

示例 3:

输入:s = "a"
输出:"a"

示例 4:

输入:s = "ac"
输出:"a"

提示:
1 <= s.length <= 1000
s 仅由数字和英文字母(大写和/或小写)组成

中心拓展算法

class Solution //中心扩展算法
public:
    pair<int, int> expandAroundCenter(const string& s, int left, int right) //以中心轴向俩侧拓展判断回文串并返回起始和终止下标,注意left=right和left=right-1的情况是通用的
        while (left >= 0 && right < s.size() && s[left] == s[right]) 
            --left;
            ++right;
        
        return left + 1, right - 1;//可以这样使用pair,用大括号,这里+1 -1是因为最后一次循环肯定不满足条件的所以才会跳出循环
    

    string longestPalindrome(string s) 
        int start = 0, end = 0;//下标记录最长回文子串的起始和终止下标
        for (int i = 0; i < s.size(); ++i) //从字符串第一位开始到最后一位分别以这个位为中心点向俩侧拓展看看以这个点为中心点能得到最长的回文串的长度是多少,由于回文串的长度可能是奇数和偶数个,所以每一次for循环要执行俩次expandAroundCenter,当是奇数个时,中心点是一个字符,传入i和i,当是偶数个时中心点是俩个字符位,传入i和i+1,expandAroundCenter返回start饿和end用一个pair,也可以自定义一个struct返回也行
            auto [left1, right1] = expandAroundCenter(s, i, i);//auto自动判断返回值的类型,C++11用法
            auto [left2, right2] = expandAroundCenter(s, i, i + 1);
            if (right1 - left1 > end - start) 
                start = left1;
                end = right1;
            
            if (right2 - left2 > end - start) 
                start = left2;
                end = right2;
            
        
        return s.substr(start, end - start + 1);
    

;

动态规划法

class Solution 
public:
//动态规划方法 dp[i][j] 表示 s[i..j] 是否是回文串,转移方程是dp[i][j]=(s[i]==s[j])&&(j-i<3||dp[i + 1][j - 1]),因为dp[i + 1][j - 1]是(i,j)内部的子串,所以已知内部,要判断dp[i][j]只需要判断俩侧即可,j-i<3是因为:
//当j-i<3,s[i...j]长度为2或3,此时不需要判断dp[i + 1][j - 1],是因为此时长度为2时只需判断俩侧即可,此时dp[i + 1][j - 1]是0所以不能判断dp[i + 1][j - 1];而长度为3时dp[i + 1][j - 1]肯定为true也不需要判断
    string longestPalindrome(string s) 
        int n = s.size();
        if (n < 2) 
            return s;
        
        int maxLen = 1;
        int begin = 0;//用记录初试下标和字符串长度来记录最长回文子串
        // dp[i][j] 表示 s[i..j] 是否是回文串,所以有效的dp得i<=j
        vector<vector<int>> dp(n, vector<int>(n));
        // 初始化:所有长度为 1 的子串都是回文串,其余为0
        for (int i = 0; i < n; i++) 
            dp[i][i] = true;
        
        // 递推开始,为什么要用这种奇怪的二重循环?用L当初始项?,观察得知判断dp[i][j]我们可能需要参考dp[i+1][j-1],把二维数组看成矩阵,[i+1][j-1]位于[i][j]的左下方,所以我们不能一行一行的用for(i)for(j)去给dp赋值,而是应该按照列优先的原则,这样只有把第j列的dp值赋完,来到下一列循环就不会导致我们需要参考的dp[i+1][j-1]还没有赋值,是本种做法的一个难点.
        for (int L = 2; L <= n; L++) // 先枚举子串长度,L表示子串的长度,i、j表示左右边界
            for (int i = 0; i < n; i++) // 枚举左边界,左边界的上限设置可以宽松一些
                // 由 L 和 i 可以确定右边界j,即 j - i + 1 = L 得
                int j = L + i - 1;
                if (j >= n) // 如果右边界越界,就可以退出当前循环
                    break;
                
                if (s[i] != s[j]) //先直接看最俩端俩侧是不是,不是直接false,都不用看dp[i + 1][j - 1],这样可以省点时间,是个优化细节
                    dp[i][j] = false;
                 else 
                    if (j - i < 3) 
                        dp[i][j] = true;
                     else 
                        dp[i][j] = dp[i + 1][j - 1];
                    
                
                // 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
                if (dp[i][j] && j - i + 1 > maxLen) 
                    maxLen = j - i + 1;
                    begin = i;
                
            
        
        return s.substr(begin, maxLen);
    
;

以上都是O(n^2)还有一种Manacher 算法可以达到O(n)

以上是关于力扣5. 最长回文子串 中心拓展算法+动态规划法的主要内容,如果未能解决你的问题,请参考以下文章

Leetcode 5. 最长回文子串

最大回文子串匹配:暴力算法中心拓展法动态规划manacher算法

5. 最长回文子串-中心扩散动态规划马拉车-字节跳动高频题

[LeetCode] 647. 回文子串 ☆☆☆(最长子串动态规划中心扩展算法)

516. 最长回文子序列(Python)

最长回文子串 (动态规划法中心扩展算法)