计算最长回文子串: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);
}
};