Longest Palindromic Substring
Total Accepted: 62794 Total Submissions: 303283Given 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.
这道题的核心是一个最长回文算法–Manacher算法 , 这个算法可以在 O(n) 时间内找出字符串中的最长回文. 我们对字符串s维护一个数组p, 其中p[i]表示的是以s[i]这个元素为中心最长的回文半径. 例如acbca, 以b为中心最长的回文半径就是3(包括了b本身). 回文可以根据字符数的奇偶分为两类, 而在Manacher算法中巧妙地将它们都划为一类. 方法是在每个字符之间添加一个’#’, 当然也可以是其它任何不在该字符串中出现的字符. 所以acbca就变成了#a#c#b#c#a#. 而#a#c#b#c#a#所对应的p就是:1 2 1 2 1 6 1 2 1 2 1. 而这样的p有一个很有用的特性,就是原字符串中的字符所对应的p的值减1,就是在原串中以它为中心的最长回文长度。例如b对应的p的值是6,而在acbca中,以b为中心的最长回文长度就是5。所以只要我们有了这个p数组,那我们从头到尾扫一遍,找出最大值,那么最大值对应的字符就是最长回文的中心,最大值减1就是回文长度。如果最长回文是偶数长度,例如abba,那么最大值对应的字符就是’#’,位于b和b之间,#a#b#b#a#, 不过这并不影响性质,最大值是5,对应的最长回文是4。
那么现在的问题就变成如何来求出这个p数组了。假设我们现在要求p[i],p[0]到p[i-1]都已经知道了。这个算法充分利用了之前求出的回文长度。
以s[id]为中心的回文的范围就是[id-p[id]+1,id+p[id]-1],假设id是当前已知的i个回文中,回文右端值最大的一个,如图所示。如果i小于等于mx,说明i是在id的回文范围内的,我们可以求出i关于id的对称点j,j = 2*id - 1, 根据回文的性质,j也必然在回文的范围内。由于p[j]的值已知,那么我们就可以用p[j]的值做个参考,如果i+p[j]< mx,那么可以断定p[i]=p[j],因为是完全对称的,如图:以j为中心的最长回文的最右端的元素的右边一个元素a和以i为中心的最长回文的最左端的元素的左边一个元素b是相对于id对称的,所以a=b。同理,以j为中心的最长回文的最左端的元素的左边一个元素c和以i为中心的最长回文的最右端的元素的右边一个元素d是相对于id对称的,所以c=d。因为a
≠
c,否则以j为中心的回文就可以继续延长。所以b
≠
d,因此以i为中心的回文也无法延长,所以p[i]=p[j]。
另外一种可能,如果i+p[j]> mx,如图所示。这种情况下,以i为中心的回文的长度同样确定,我们用right,left来表示以id为中心的回文的最右元素的右边一个元素和最左元素的左边一个元素。我们用rightJ表示left元素关于j的对称元素,用leftI表示right元素关于i的对称元素。而rightJ和leftI是关于id对称的,所以rightJ = leftI,由于left = rightJ,所以left = leftI,已知right
≠
left, 否则回文可以继续延长,所以right
≠
leftI,因此以i为中心的回文不能在延长。因此我们得到回文最右端位置,已知中心位置,最长回文半径直接可以求出。
另外还有一种情况是i+p[j]= mx,这种情况下是否在能延长就说不清了,因为left ≠ rightJ,因此leftI ≠ left,已知right ≠ left,所以leftI是否等于right就说不清。此时就只能一个一个地检验了,即:检测leftI和right是否相等,如果相等继续向两边延长,若不相等,则找到回文最右端位置,已知中心位置,最长回文半径直接可以求出。
还有最后一种情况,如果i大于mx,那么之前已求出的回文长度就派不上用场了,此时,也只能以i为中心向两边一个一个地检测了。
最后附上完整代码:
#include "stdio.h"
#include "stdlib.h"
#include <iostream>
#include <cstring>
using namespace std;
char* longestPalindrome(char* s)
{
int length = strlen(s);
char *s2 = (char*)malloc(sizeof(char) * (length * 2 + 1 +2 + 1));
s2[0] = '$';
int j = 1;
for(int i = 0; i < length; i++)
{
s2[j++] = '#';
s2[j++] = s[i];
}
s2[j++] = '#';
s2[j++] = '&';
s2[j] = '\0';
int len2 = strlen(s2);
int *p = (int*)malloc(sizeof(int) * len2);
int r = 0, right = 0;
for(int id = 1; id < len2 - 1; id++)
{
if(id <= right)
{
if(p[2*r-id]+id-1 > right)
{
p[id] = right - id + 1;
}
else if(p[2*r-id]+id-1 < right)
{
p[id] = p[2*r-id];
}
else
{
while(s2[right] == s2[2 * id - right])
{
right++;
r = id;
}
right--;
p[id] = right - id + 1;
}
}
else
{
int i = 0;
while(s2[id + i] == s2[id - i])
i++;
r = id;
right = id + i - 1;
p[id] = i;
}
}
int largest = 0, largestId, largestLen;
for(int id = 1; id < len2 - 1; id++)
{
if(p[id] > largest)
{
largest = p[id];
largestId =id;
largestLen = p[id] - 1;
}
}
cout<<largestId <<" "<<largestLen<<endl;
if(largestId % 2 == 0)
{
s[largestId/2 + largestLen/2] = '\0';
return s + (largestId/2-1 - largestLen/2);
}
else
{
s[largestId/2 + largestLen/2] = '\0';
return s + (largestId/2 - largestLen/2);
}
}
int main()
{
char s[1000010];
char *p;
int cas = 1;
while(scanf("%s", s) != EOF)
{
if(strcmp(s, "END") == 0)
break;
p = longestPalindrome(s);
cout<<p;
}
return 0;
}