leetcode——Longest Palindromic Substring

题目:Given a string S, find the longest palindromic substring in S. You may assume that the maximum length of S is 1000, and there exists one unique longest palindromic substring.

大意是:从所给的字符串s中找出其最长回文字串

例如:s = “aabcba”, 则最长回文字串为”abcba”

思路1:

    我一开始的思路:遍历s,找出两个相同字母之间的最长回文字串,因此要记录后一个字母的位置,将其存到一个map里。但是还是超时

class Solution {
public:
    string longestPalindrome(string s) {
        // 用map记录每个字母的位置
        // 遍历s,找出后面同一个字母的位置,看其之间是否是回文
        unordered_map<char, vector<int> > index;
        for(int i = s.size()-1; i >= 0; i--){
            char c = s[i];
            vector<int> tmp;
            if(index.find(c) != index.end()) tmp = index[c];
            tmp.push_back(i);
            index[c] = tmp;
        }
        int maxRet(1);
        string maxSub(""+s[0]);
        for(int i = 0; i != s.size(); i ++){
            if(s.size() - i <= maxRet) break;
            char c = s[i];
            auto tmp = index[c];
            for(int j: tmp){ // i后面的同一个字符的索引
                if(j > i){
                    // 如果字符串本身长度都没maxRet大,则不用判断其是否是回文
                    if(j-i+1 <= maxRet) break; 
                    int count(0);
                    int k = i;
                    while(c == s[j] && k < j){
                        count+=2;
                        k++;
                        j--;
                    }
                    if(k >= j){  // 是回文字串
                        // 说明是回文字符串且中间有单个字符
                        if(k == j) count--;
                        if(maxRet < count){
                            maxRet = count;
                            maxSub = s.substr(i, count);
                        }
                    }
                }
            }
        }
        return maxSub;
    }
};

image

思路2:

后来看了网上的答案,用到Manaer’s Algorithm,时间复杂度是O(n), 参考了博文之后自己写了一遍,大致了解了算法的过程。

算法的基本思想是要把s=”babcbabcbaccba”变为T=”#b#a#b#c#b#a#b#c#b#a#c#c#b#a#”,之后在T的基础上进行查找。

如图,把s=”babcbabcbaccba”变为T之后, #的作用是不管s的长度是奇数还是偶数,使得以T[i]为中心的回文字符串都是奇数;

p[i]是以T[i]为中心的回文字符串的长度;

C是最近一次算出的最长回文字符串的中心字符的位置;

R是以C为中心的回文字符串的最右边的字符的位置,同理,L是最左边的位置,则L到R之间是以C为中心的回文字符串。

上图的意思是当i==15时,i属于以T[C]为中心的、左右两边长为9的回文字符串的范围内。

p[i]的这个长度最终由下面算出来的

while(T[i+1+p[i]] == T[i-1-p[i]]){
   p[i]++;
}

当i==0时,T[0]之前没有回文字串,因此p[0]是0,然后T[0]的左边T[-1]和右边T[1]不是同样的字符,所以p[i]还是0;

当i==1时,T[1]左右有T[0]==T[2],因此p[1]++,因此p[1]为1;

以此类推,T[3]即a的左边有3个和3个右边的字符相等,所以p[3]为3

上述p[i]的算法是扩充,初始的p[i]是下面算出来的,由初始到扩充的这样的好处是优化了时间复杂度是O(n²)的直接遍历s[i]算出以其为中心的回文字符串长度的那种算法:

int i_mirror = 2*C - i; // i` = C – (i-C)
 p[i] = R>i ? min(p[i_mirror], R-i) : 0;

这个的意思是

1. 若前面算出的以T[C]为中心的回文字符串很长,当前T[i]属于其回文中心内,即R>i,则可以找到其在回文字符串中对应的i`的p[i`],由其算出p[i], 例如,T[11]的回文串长度为9,则R则到了20,那么算T[15]时,则有两种情况
    那么其p[i]:
        1)可能就是其前面镜像i`的p[i`]的长度,例如T[12]的b则和T[10]的b是回文,则p[12]和p[10]的相等,
        2)可能是R-i的长度,但是以T[i`]为中心的回文字符串可能不只在T[C]的回文字符串的范围内,例如
    因此p[i]应为1)和2)中最小的情况,之后再以T[i]为中心重新扩充p[i]
2. 若T[i]不在以T[C]为中心的回文字符串内,则其p[i]为0

每次扩充完p[i]之后,以T[i]为中心的回文字符串的R可能就要超出以T[C]为中心的字符串的R了,因此要更新C和R,这样能保证算出i的镜像i`的位置是当前回文字符串中算出来的

if(i+p[i] > R){
    C = i;
    R = i + p[i];
 }

之后找出T中p[i]最大值maxVal以及最大值的位置index,这样能用index算出s中回文字符串的开始位置,即(index-maxVal)/2,例如上图是(11-9)/2 = 1;

因此s=”babcbabcbaccba”,即从s[1]的a开始,长度为9:“abcbabcba”

下面是完整代码,注意头加入’^’尾加入’$’作为开始和结束的标志,因此遍历T是是[1, n-1),而最后算回文字符串开始位置时要多-1,减去开始’^’的占位

class Solution {
    // 将s="aba"变为T="^#a#b#a#&",其中"^$"为字符串开始和结束符,
    // #的作用是不管s的长度是奇数还是偶数,使得以T[i]为中心的回文字符串都是奇数
    // 其中T[i]可能是字母也可能是'#'
public:
    string longestPalindrome(string s) {
        // 将s变为T
        string T = S2T(s);
        // 计算以T[i]为中心的最大回文字符串的长度p[i]
        const int n = T.size();
        // C是最近一次算出的最长回文字符串的中心字符的位置
        // R是最近一次算出的最长回文字符串的最右边的字符的位置
        int p[n], C(0), R(0); 
        // 1. 若前面算出的以T[C]为中心的回文字符串很长,当前T[i]属于其回文中心内,即R>i,
        // 那么其p[i]:(如上图)
        //  1)可能就是其前面镜像i`的p[i`]的长度
        //  2)可能是R-i的长度,但是以T[i`]为中心的回文字符串可能不只在T[C]的回文字符串的范围内
        //  因此p[i]应为1)和2)中最小的情况,之后再以T[i]为中心重新扩充p[i]
        // 2. 若T[i]不在以T[C]为中心的回文字符串内,则其p[i]为0
        for(int i = 1; i < n-1; i ++){
            int i_mirror = 2*C - i; // i` = C - (i-C)
            p[i] = R>i ? min(p[i_mirror], R-i) : 0;
            // 扩充p[i]
            while(T[i+1+p[i]] == T[i-1-p[i]]){
                p[i]++;
            }
            // 若以T[i]为中心的字符串的范围走出了以T[C]为中心的字符串的范围,则更新C,R
            if(i+p[i] > R){
                C = i;
                R = i + p[i];
            }
        }
        // 算好p[i]之后找出最大的p[i]所在的i的位置index,则算出回文字符串在原s中的开始字符的位置
        // 之后得出其最大回文子串
        int maxVal(0), index(0);
        for(int i = 1; i < n-1; i ++){
            if(maxVal < p[i]){
                maxVal = p[i];
                index = i;
            }
        }
        return s.substr((index-1-maxVal)/2, maxVal);
        
    }
    string S2T(const string &s){
        if(s.size() == 0) return "^$";
        string T = "^";
        for(int i = 0; i != s.size(); i ++){
            T += "#" + s.substr(i, 1);
        }
        T += "#$";
        return T;
    }
};
原文地址:https://www.cnblogs.com/skysand/p/4289763.html