给定一个子符串S,找出最长回文子串。
用中心扩展法求最长回文子串,可以达到O(N2)的时间复杂度。而Manacher算法可以进一步达到O(n)。
算法特点:
1. 将偶数和奇数长度回文字符串都转化成奇数长度回文字符串统一处理。这是通过在每相邻字符之间插入分隔字符达到的。
如:S = “abaaba”, T = “#a#b#a#a#b#a#”
2. 以每个字符为中心,向外扩展回文。方法同中心扩展法一样。但Manacher算法引入一个辅助数组,来避免重复劳动。
引入辅助数组P,P[i]表示以字符T[i]为中心的回文串的长度(半径)。
请看以下示意图:
当开始扩展以T[i]为中心的回文字符串时,就可以利用辅助数P避免重复劳动。
if (mx > i)
P[i] = min(P[j],mx-i); // 利用已有成果避免重复劳动
else
P[i] = 0;
while (T[i+P[i]+1] == T[i-P[i]-1])
P[i]++; // 以i为中心向两边扩张
string longestPalindrome(string s) {
string T;
T.reserve(s.length()*2+3);
if (s.length() == 0)
T = "^&";
else
{
T = "^#";
for (int i=0;i<s.length();i++)
{
T += s[i];
T += '#';
}
T += "&";
}
int *P = new int[T.length()];
int id = 0;
int mx = 0;
for (int i=0; i<T.length(); i++)
{
if (mx > i)
P[i] = min(P[id*2-i],mx-i);
else
P[i] = 0;
while (T[i+P[i]+1] == T[i-P[i]-1])
P[i]++;
if (P[i]+i > mx)
{
mx = P[i];
id = i;
}
}
mx = 0;
id = 0;
for (int i=0; i<T.length(); i++)
{
if (P[i] > mx)
{
mx = P[i];
id = i;
}
}
delete[] P;
return s.substr((id-mx+1)/2-1,mx);
}
算法首先将字符串S转化成T。
例如:S = "abba", T = "^#a#b#b#a#$".
^和$是哨兵自符,可以避免越界条件检查。
在求出转化后字符串T的最长回文子串后,如何取掉那些添加的分隔符呢?
答案是不用去除,直接从源串S中取相应的子串就可以。
这就涉及到一个下标映射的问题。
设S串的下标i,对应字符S[i]在T串的位置为j = i * 2 + 2 (算上^)
则 i = j/2-1
在T中,最大回文子串为[id-mx, id+mx]
引用上面公式,在S中,则对应子串为[(id-mx)/2-1, (id+mx)/2-1]
考虑到回文子串最前最后必为添加的分隔符,故需对上面作一个裁剪修正。
[(id-mx+1)/2-1, (id+mx-1)/2-1]
这就是为什么在函数最后返回
s.substr((id-mx+1)/2-1,mx);
的一个简单推导。
参考资料:
http://blog.csdn.net/ggggiqnypgjg/article/details/6645824
http://leetcode.com/2011/11/longest-palindromic-substring-part-ii.html