LeetCode题解 —— 5. 最长回文子串

题目描述

给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。

示例 1:

输入: "babad"
输出: "bab"
注意: "aba" 也是一个有效答案。

示例 2:

输入: "cbbd"
输出: "bb"

解题思想

考虑三种解决方案:

1.动态规划

设状态dp[j][i]表示索引j到索引i的子串是否是回文串。则易得转移方程如下:
d p [ j ] [ i ] = { t r u e , j = i s t r [ i ] = = s t r [ j ] , i - j = 1 s t r [ i ] = = s t r [ j ] & & d p [ j + 1 ] [ i − 1 ] , i - j > 1 dp[j][i] = \begin{cases} true, & \text{j = i} \\ str[i] == str[j], & \text{i - j = 1} \\ str[i] == str[j] \&\& dp[j + 1][i - 1], & \text{i - j > 1} \\ \end{cases} dp[j][i]=true,str[i]==str[j],str[i]==str[j]&&dp[j+1][i1],j = ii - j = 1i - j > 1
则dp[j][i]为true时表示索引j到索引i形成的子串为回文子串,且子串起点索引为j,长度为i - j + 1。

2.中心扩展

枚举中心位置,然后再在该位置上向左右扩展,找到最长的回文子串,注意区分回文子串长度为奇数和偶数的两种情况。

3.Manacher算法

Manacher算法首先通过在每个字符的两边都插入一个特殊的符号,将所有可能的奇数或偶数长度的回文子串都转换成了奇数长度。比如"abba"变成"#a#b#b#a#",“aba"变成”#a#b#a#"。

以字符串s = "babad"为例,插入#这个特殊字符后,变成 t = “#b#a#b#a#d#”,然后用一个数组p[i]来记录以t[i]为中心的最长回文子串向左或向右扩张的长度(包括t[i]),即p[i]为新字符串t在t[i]处的回文半径。

i012345678910
t#b#a#b#a#d#
p12141412121

可以看到,p[i] - 1就是以t[i]为中心的回文子串在原字符串s中的长度。可以如下证明之:
首先在转换得到的字符串t中,所有的回文子串的长度都为奇数,那么对于以t[i]为中心的回文字串,其长度就为2*p[i] - 1,又可以观察到,t中所有的回文子串,其中分隔符的数量一定比其他字符的数量多1,也就是有p[i]个分隔符,剩下p[i] - 1个字符来自原字符串,所以该回文串在原字符串中的长度就为p[i]-1。

所以原问题就转化为求p[i] - 1的最大值。

Manacher算法增加两个辅助变量id和mx,其中id表示能延伸到最右端位置的那个回文子串的中心点位置,mx则为id + p[id],也就是回文子串能延伸到的最右端位置(即将到达但还没有到达)。得到一个很重要的结论:

如果mx > i,那么p[i] >= min(p[2 * id - i], mx - i)

下面来证明上述结论,令j = 2 * id - i,也就是说j是i关于id的对称点。

  • 当mx - i > p[j] 的时候,以t[j]为中心的回文子串包含在以t[id]为中心的回文子串中,由于i和j对称,以t[i]为中心的回文子串必然包含在以t[id]为中心的回文子串中,所以必有p[i] = p[j];
    image

  • 当p[j] >= mx - i 的时候,以t[j]为中心的回文子串不一定完全包含于以t[id]为中心的回文子串中,但是基于对称性可知,下图中两个绿框所包围的部分是相同的,也就是说以t[i]为中心的回文子串,其向右至少会扩张到mx的位置,也就是说p[i] >= mx - i。至于mx之后的部分是否对称,再具体匹配。
    image

  • 对于mx <= i 的情况,因为无法对p[i]做更多的假设,只能让p[i] = 1,然后再去匹配。

解题代码

class Solution {
public:
	//DP
    string longestPalindrome(string s) {
        if(s.empty())
            return "";
        int n = s.size();
        vector<vector<bool>> dp(n, vector<bool>(n, false));
        int start = 0;
        int maxLen = 0;
        for(int i = 0; i < n; i++)
            for(int j = 0; j <= i ; j++){
                if(i - j < 2)
                    dp[j][i] = (s[i] == s[j]);
                else
                    dp[j][i] = (s[i] == s[j]) && dp[j + 1][i -1];
                if(dp[j][i] && i - j + 1 > maxLen){
                    start = j;
                    maxLen = i - j + 1;
                }
            }
        return s.substr(start, maxLen);
    }

	//中心扩展
	string longestPalindrome2(string s) {
        if(s.empty())
            return "";
        int n = s.size();
        int start = 0;
        int maxLen = 0;
        for(int i = 0; i < n; i++){
            //奇数扩展
            for(int j = 0; i - j >= 0 && i + j < n; j++){
                if(s[i - j] != s[i + j])
                    break;
                if(maxLen < 2 * j + 1){
                    start = i - j;
                    maxLen = 2 * j + 1;
                }
            }
            //偶数扩展
            for(int j = 0; i - j >= 0 && i + j + 1 < n; j++){
                if(s[i - j] != s[i + j + 1])
                    break;
                if(maxLen < 2 * j + 2){
                    start = i - j;
                    maxLen = 2 * j + 2;
                }
            }
        }
        return s.substr(start, maxLen);
    }
	
	//马拉车算法
	string longestPalindrome3(string s) {
        if(s.empty())
            return "";
        //预处理
        string t = "#";
        for(auto c : s){
            t += c;
            t += "#";
        }
        int n = t.size();
        int p[n];
        int start = 0;
        int maxLen = 0;
        int id = 0; //能延伸到最右端位置的那个回文子串的中心点位置
        int mx = 0; //回文子串能延伸到的最右端位置
        for(int i = 0; i < n; i++){
            // mx - i > p[2 * id - i]时,p[i] = p[2 * id - i]
            // mx - i <= p[2 * id - i]时, p[i] >= mx - i
            // 即mx > i 时,p[i] >= min(p[2 * id - i, mx - i])
            p[i] = mx > i ? min(p[2 * id - i], mx - i) : 1;
            while(i - p[i] >= 0 && i + p[i] < n && t[i - p[i]] == t[i + p[i]])
                p[i]++;
            if(mx < i + p[i]){
                mx = i + p[i];
                id = i;
            }
            if(maxLen < p[i] - 1){
                maxLen = p[i] - 1;
                start = (i - p[i] + 1) / 2;
            }
        }
        return s.substr(start, maxLen);
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
回答: 最长回文子串可以通过两种方法来实现。第一种是使用中心扩展法,代码如下: ```python class Solution: def check(self, s, l, r): while l >= 0 and r < len(s) and s[l == s[r]: l -= 1 r += 1 return l + 1, r - 1 def longestPalindrome(self, s: str) -> str: start, end = 0, 0 for x in range(len(s)): l1, r1 = self.check(s, x, x) l2, r2 = self.check(s, x, x + 1) if r1 - l1 > end - start: start, end = l1, r1 if r2 - l2 > end - start: start, end = l2, r2 return s[start:end+1] ``` 第二种方法是使用动态规划,代码如下: ```python class Solution: def longestPalindrome(self, s: str) -> str: res = '' for i in range(len(s)): start = max(0, i - len(res) - 1) temp = s[start:i+1] if temp == temp[::-1]: res = temp else: temp = temp<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [5. 最长回文子串(Python 实现)](https://blog.csdn.net/d_l_w_d_l_w/article/details/118861851)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [LeetCode(Python3)5.最长回文子串](https://blog.csdn.net/weixin_52593484/article/details/124718655)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [力扣 (LeetCode)刷题笔记5.最长回文子串 python](https://blog.csdn.net/qq_44672855/article/details/115339324)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值