1. 程式人生 > >LeetCode演算法題5:最長迴文子串解析

LeetCode演算法題5:最長迴文子串解析

給定一個字串 s,找到 s 中最長的迴文子串。你可以假設 s 的最大長度為 1000。
示例 1:

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

示例 2:

輸入: "cbbd"
輸出: "bb"

這個題可以暴力法搜尋,設定一個數組,儲存每個位置字元為中心的迴文字串長度,最後輸出最長的,但是這樣的時間複雜度是O(n^2),有一種專門用來搜尋最長迴文子串的快速演算法叫Manacher演算法,其複雜度是線性的。
演算法的思路如下:(來自百度百科)
Manacher演算法提供了一種巧妙的辦法,將長度為奇數的迴文串和長度為偶數的迴文串一起考慮,具體做法是,在原字串的每個相鄰兩個字元中間插入一個分隔符,同時在首尾也要新增一個分隔符,分隔符的要求是不在原串中出現,一般情況下可以用#號。
新增分隔符


輔助陣列:
Manacher演算法用一個輔助陣列Len[i]表示以字元T[i]為中心的最長迴文字串的最右字元到T[i]的長度,比如以T[i]為中心的最長迴文字串是T[l,r],那麼Len[i]=r-i+1。如下圖:
輔助陣列
Len 陣列有一個性質,那就是Len[i]-1就是該回文子串在原字串S中的長度。
Len陣列的計算:
首先從左往右依次計算Len[i],當計算Len[i]時,Len[j] (0<=j<i)已經計算完畢。設P為之前計算中最長迴文子串的右端點的最大值,並且設取得這個最大值的位置為po,分兩種情況:
第一種情況:i<P
那麼找到i相對於po的對稱位置,設為j,那麼如果Len[j]<P-i,如下圖:
在這裡插入圖片描述

那麼說明以j為中心的迴文串一定在以po為中心的迴文串的內部,且j和i關於位置po對稱,由迴文串的定義可知,一個迴文串反過來還是一個迴文串,所以以i 為中心的迴文串的長度至少和以j為中心的迴文串一樣,即Len[i]>=Len[j]。因為Len[j]<P-i,所以說i+Len[j]<P。由對稱性可知Len[i]=Len[j]。
如果Len[j]>=P-i,由對稱性,說明以i為中心的迴文串可能會延伸到P之外,而大於P的部分我們還沒有進行匹配,所以要從P+1位置開始一個一個進行匹配,直到發生失配,從而更新P和對應的po以及Len[i]。
所以其實Len[i]取的是Len[j]與P-i的最小值。
第二種情況:i>=P

如果i比P還要大,說明對於中點為i的迴文串還一點都沒有匹配,這個時候,就只能老老實實地一個一個匹配了,匹配完成後要更新P的位置和對應的po以及Len[i]。
在這裡插入圖片描述
所以思路也就是這樣了,程式也就出來了。
C++原始碼:

class Solution {
public:
    string longestPalindrome(string s) {
        string T = "*#";
        for(int i=0;i<s.length();i++)
        {
            T += s[i];
            T += "#";
        }
        int maxLen = 0, maxn = 0;
        int P = 0;
        int po = 0;
        int p[T.length()] = {0};
        for(int i=0;i<T.length();i++)
        {
            if(P>i) 
                p[i] = min(p[2*po-i], P-i);
            else
                p[i] = 1;
            while(T[i-p[i]]==T[i+p[i]])
                p[i]++;
            if(p[i]+i>P)
            {
                P = p[i]+i;
                po = i;
            }
            if(maxLen<p[i])
            {
                maxLen = p[i];
                maxn = i;
            }
        }
        return s.substr((maxn-maxLen)/2, maxLen-1);
    }
};

python3原始碼:

class Solution:
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        T = "*#"
        for i in range(len(s)):
            T += s[i]
            T += "#"
        T += "$"
        po = 0
        maxLen = 0
        maxn = 0
        p = [0]*len(T)
        for i in range(2, len(T)-1):
            if p[po]+po>i:
                p[i] = min(p[po*2-i], p[po]+po-i)
            else:
                p[i] = 1
            while T[i-p[i]]==T[i+p[i]]:
                p[i] += 1
            if p[i]+i>p[po]+po:
                po = i
            if p[po]>maxLen:
                maxLen = p[po]
                maxn = po
        return s[(maxn-maxLen)//2:(maxn-maxLen)//2+maxLen-1]