計算最長回文子串:manacher算法
題目:
Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.
Example:
Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer.
Example:
Input: "cbbd"
Output: "bb"
以下字元串中的空格隻是為了看起來友善。
-
回文串分為奇數長度的串和偶數長度的串。
a b c b c len = 5
a b c c b c len = 6
為了避免分别考慮這兩種情況。manacher算法通過采用預處理的方式。
預處理:插入特殊字元(原串中不存在的字元)
原串:a b c b c len = 5
預處理後:# a # b # c # b # c # Len = 52 +1 = 11
原串:a b c c b c len = 6
預處理後:# a # b # c # b # c # Len = 62 +1 = 13
可以發現預處理後,串的長度都變成了奇數(預處理可以看成在串的每個字元前面加上"#",再在串的末尾加上一個"#’,是以預處理後的長度為Len= 2*len +1,永遠都是奇數)
-
回文串的表示方法 :采用pos + len的方法。
maxpos指回文串的中心位置的下标,maxlen指回文串的單向長度
原串:a b c b c
pos = 2(注意是原串的下标)
len = 5(原串中回文串的長度)
預處理後的串:# a # b # c # b # c #
maxpos = 5(預處理後的中心位置的下标,注意是下标,從0開始)
maxlen = 6(預處理後的單向長度,注意是長度,從1開始)
可以發現:
pos = maxpos / 2;
maxlen = len - 1;
我們就可以根據預處理後求出的回文串對應的原串的長度和下标了
-
得到原串的長度和下标後,我們就可以計算原串中回文串的[begin,end)的值
原串:e a b c b a f
pos = 3;(pos:原串中回文串中心位置下标)
len = 5;(len:原串中回文串的長度)
begin = pos - len / 2 = 3 - 5/2 = 1 ;
end = pos + (len + 1) / 2 = 3+ 3 = 6;
得到 [begin, end) 為[1,6)
-
下面是manacher算法的主要部分
對于處理後的串計算一個RL數組(儲存以每一個字元為中心位置的回文串的單向長度,這裡是向右取長度right-length即RL)
先把RL數組初始化為1,因為至少包括本身.
如:# a # b # c # b # c #
對于a:# a # ,是以RL[1] = 2;
最終RL[11]={1,2,1,2,1,6,1,2,1,2,1}
maxpos = 5;
maxlen = RL[maxpos] = 6;
此時可計算對應的原串的[begin,end)
原串:a b c b a
pos = maxpos / 2 = 2;(原串中回文串的中心位置下标)
此時可得到對應的原串中[begin,end)的值:
begin = pos - len / 2 = 2 - 2 = 0;
end = pos + (len + 1)/2 = 2 +3= 5;
得到:[0,5)
計算RL數組
從maxpos開始向左右擴充,判斷是否相等,若相等,說明單向長度可以增加1,然後在繼續判斷…
優化manachar算法到O(n)
參考大神的部落格
http://blog.tk-xiong.com/archives/1181
class Solution {
public:
string manacher(string s)
{
//預處理
string str = "#";
for(int i = 0; i < s.length(); i++)
{
str += s.substr(i,1);
str += "#";
}
vector <int> RL(str.length(),1);
int MaxRight = 0; //向右覆寫的回文串的最右邊的下标
int MaxRightPos = 0; //預處理後回文字元串的中心下标
int MaxLen = 0; //向右單向長度
int MaxPos = 0;
for(int i = 0;i < str.length(); i++)
{
if(i < MaxRight)
{
RL[i] = min(RL[MaxRightPos*2 - i],MaxRight - i);
}
while((i-RL[i] >= 0) && (i+RL[i] < str.length()) && str[i+RL[i]] == str[i-RL[i]])
{
RL[i] += 1;
}
//更新回文串最右邊下标MaxRight。減1是因為RL[i]包括了i本身占了1位
if(i+RL[i]-1 > MaxRight)
{
MaxRight = i+RL[i]-1;
MaxRightPos = i;
}
//更新最長回文串
if(RL[i] >MaxLen)
{
MaxLen = RL[i];
MaxPos = i;
}
}
int pos = MaxPos / 2;
int len = MaxLen - 1;
int begin = pos - len / 2;
string rt = s.substr(begin,len);
return rt;
}
string longestPalindrome(string s)
{
return manacher(s);
}
};