1. 回文串定义
回文串是一个正读和反读都一样的字符串,比如“aba”或者“abba”等等就是回文串。
2. 最长回文子串方法
最长回文子串的长度方法可以有三种方法:
1) 朴素算法是依次以每一个字符为中心向两侧进行扩展,时间复杂度是O(N^2)的;
2) 利用扩展的KMP算法,时间复杂度也很快O(N*logN)。
3) manacher算法,该算法是专门针对回文子串的算法,其时间复杂度为O(n)。
3. 重点讲述manacher算法
3.1 manacher算法的优点:
1) manacher算法把奇数的回文串和偶数的回文串统一起来了,通过加入特殊字符使得所有字符串统一变成了奇数长度的回文串。
2) manacher算法利用前面字符的回文长度的对称性求当前的字符的回文长度,减少了重复比较。
3.2 manacher算法的步骤
1) 首先在原来字符串的每两个相邻的字符中间掺入一个分隔符,这个分隔符没有在原串中出现过,一般可以用’#’分割。这样就很巧妙的把奇数长度和偶数长度的回文串统一成奇数长度的新的回文串了。例如:aba变为#a#b#a#, abba变为#a#b#b#a;
2) 然后利用一个辅助数组P[]来记录以每个字符为中心的最长回文串的信息。假定str[]为步骤1执行后构造好的新字符串,P[i]表示以str[i]为中心的最长回文串的长度,即当以str[i]为中心时,以str[i]为第一个字符,向右或者向左延伸p[i]个长度。那么p[i] – 1就是该回文串在原串中的长度,这是因为以str[i]为中心的回文串在新串中长度为2 * p[i] – 1,那么由于新串中的每一个回文子串#个数都比相应的源字符串个数多一个,所以原串中回文长度为(2 * p[i] – 1 - 1) / 2 = p[i] – 1;
3) 求辅助数组P[]的值,我们要利用以前已经求出的结果来计算当前的p[i]的值。利用maxID记录在i之前的回文串中延伸到最右端的位置,同时用ID来记录去的最右端的maxID对应的下标id. 为了防止字符比较越界,我们在新串的第一位前面加了一个特殊的字符’$’.
在这里给出求解P[i]的核心代码:
if (maxID > i)
{
P[i] = min(p[2 * ID - i], maxID - i);
}
Else
{
P[i] = 1;
}
也就是说当以前的某一个ID对应的左右端位置maxID大于当前i的时候,p[i]已经有了最小值min( p[2 * ID - i], maxID - i)。
当此时P[i]取值有2种情况时,如图1和图2所示:
图 2
在图1中,I和j 关于ID对称,由于I, j都完全在[2 * ID – maxID, maxID]的范围内, 并且在[2 * ID – maxID,maxID]范围内的字符串必定是回文字符串,由于对称性可知,如果j处有回文字符串,那么在I处必然有与j处一样的相应的回文字符串,这就像照镜子一样, j处必然有i处的镜像。我们可以得到p[i] = p[j] 即p[i] = p[2 * ID - i]。
在图2中,以ID为中心,如果I, j 没有完全在[2 * ID – maxID, maxID]的范围内,那么i处的回文必然不能完全和j处的一样了,此时i处的回文长度为maxID –i。
综合图1和图2, 所以当maxID > i时有P[i] = min( p[2 * ID - i], maxID - i);
当然,根据前面已经算出的结果可以推出P[i]的最小值,我们仍然要在当前p[i]的最小值基础上,以str[i]为中心,向两侧扩展,直到p[i]达到其最大值。
至此manacher算法的求解过程结束。
下面给出manacher算法的完整代码:
int Manacher(char * OldStr)
{
int i, M, n;
int MaxId, Id, MaxValue;
n = strlen(OldStr);
newStr[0] = '$';
newStr[1] = '#';
for (i = 1;i <= n; ++i)
{
newStr[i << 1] = OldStr[i - 1];
newStr[(i << 1) + 1] = '#';
}
M = ((n + 1) << 1);
newStr[M] = 0;
MaxValue = MaxId = 0;
for (i = 1;i < M; ++i)
{
if (MaxId > i)
{
p[i] = min(p[2 * Id - i], MaxId - i);
}
else
{
p[i] = 1;
}
while(newStr[i + p[i]] == newStr[i - p[i]])
{
++p[i];
}
if (p[i] + i > MaxId)
{
MaxId = p[i] + i;
Id = i;
}
if (p[i] > MaxValue)
{
MaxValue = p[i];
}
}
return MaxValue - 1;
}
现在利用上面的知识求解hdu 3608最长回文 来求解 最长回文子串:
完整代码如下:
4. hdu 3068 最长回文
#include <iostream>
using namespace std;
//Manacher算法
#define N 110010
int p[2 * N];
char newStr[2 * N];
char OldStr[N];
int Manacher(char * OldStr)
{
int i, M, n;
int MaxId, Id, MaxValue;
n = strlen(OldStr);
newStr[0] = '$';
newStr[1] = '#';
for (i = 1;i <= n; ++i)
{
newStr[i << 1] = OldStr[i - 1];
newStr[(i << 1) + 1] = '#';
}
M = ((n + 1) << 1);
newStr[M] = 0;
MaxValue = MaxId = 0;
for (i = 1;i < M; ++i)
{
if (MaxId > i)
{
p[i] = min(p[2 * Id - i], MaxId - i);
}
else
{
p[i] = 1;
}
while(newStr[i + p[i]] == newStr[i - p[i]])
{
++p[i];
}
if (p[i] + i > MaxId)
{
MaxId = p[i] + i;
Id = i;
}
if (p[i] > MaxValue)
{
MaxValue = p[i];
}
}
return MaxValue - 1;
}
int main()
{
while(scanf("%s", OldStr) != EOF)
{
int Max = Manacher(OldStr);
printf("%d\n", Max);
}
return 0;
}