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]处的回文半径。

i 0 1 2 3 4 5 6 7 8 9 10
t # b # a # b # a # d #
p 1 2 1 4 1 4 1 2 1 2 1

可以看到,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];

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

  • 对于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);
    }
};

你可能感兴趣的:(LeetCode,字符串,动态规划)