Manachar算法主要是处理字符串中关于回文串的问题的,它可以在 O(n) 的时间处理出以字符串中每一个字符为中心的回文串半径.因为它只对没有匹配过的位置进行匹配。
我们在字符串中的每一个字符前后 都加一个特殊符号,例如 “#”,然后在字符串最后再加一个。那么就让字符串变成奇数了。因为如果字符串长度为n的话, “#” 的数量为n+1
2*n+1 为奇数。
-
例如原字符串为 “ababa” 长度为5,奇数。
-
处理后的字符串为“ #a#b#a#b#a# ” 长度变为11,奇数。
-
原字符串为“abba” ,长度为4,偶数。
-
处理后的字符串为 “#a#b#b#a#” 长度为9,奇数。
-
这样就不用考虑奇偶问题了。因为处理后的字符串恒为奇数。
Len数组有一个性质,那就是Len[i]-1就是该回文子串在原字符串S中的长度,所有的回文字串的长度都为奇数,那么对于以T[i]为中心的最长回文字串,其长度就为2*Len[i]-1,T中所有的回文子串,其中分隔符的数量一定比其他字符的数量多1,也就是有Len[i]个分隔符,剩下Len[i]-1个字符来自原字符串,所以该回文串在原字符串中的长度就为Len[i]-1。
首先对数据进行初始化,再对i进行判断,分为两种情况
第一种情况,i>=mx,i在mx前面,直接让len[i]=1。
第二种情况,i<mx,这时候就又有两种情况了,对len[j]和mx-i进行比较:
(1)len[j]<=mx-i说明i的最右端还在mx里面,如上图所示,只需要让len[i]=len[j]即可。
(2)len[j]>mx说明i的最右端大于mx了,所以我们需要对这两种情况再讨论一下,当Len[j] < mx-i的时候,表示Len[i]的长度可能不会超过mx-i,所以我们就从i的Len[2*id - i]也就是Len[mx-i]的地方开始匹配。当Len[j] > mx - i的时候,说明i位置的子串长度超过了mx,但mx以外的地方还没有遍历到,所以我们就暂且将len[i]=mx-i赋值一个较小的且能够遍历到的,然后再以i为中心,以mx-i为半径向两边更新,如果len[i]+i>mx,就更新len[i]+i为最右边,中点id更新为i,再比较更新最长回文子串的长度。
#include <iostream>
#include <algorithm>
using namespace std;
const int N=3*1e7+5000;
char s[N],str[N];
int p[N];
void init()
{
int k=0;
str[k++]='@';//开头加个特殊字符防止从中间向两边扩散的时候越界
str[k++]='#'; //在每个字符前加个符号来统一奇偶
for(int i=0;s[i];i++)
{
str[k++]=s[i];
str[k++]='#'; //在每个字符后也加入一个符号来统一奇偶
}
}
int main()
{
cin>>s;
init();
int id=0,mx=0,maxx=0; //mx就是当前遍历过的最右边的位置,id表示回文子串的中心
for(int i=1;str[i];i++) //跳过第一个特殊字符
{
if(i<mx)//如果i小于mx表示还没超过mx,所以进行判断以i为中心的半径会不会大于mx
{
if(p[2*id-i]<mx-i) //根据之前求过的i的对称点j判断i的半径不会大于mx-i
p[i]=p[2*id-i]; //比较取一个短的
else p[i]=mx-i;
}
else p[i]=1; //大于了就只能保证本身是回文
while(str[i+p[i]]==str[i-p[i]]) //向两边进行扩散
p[i]++;
if(i+p[i]>mx) //如果超出当前的最右边就更新
{
id=i;
mx=i+p[i];
maxx=max(maxx,p[i]);
}
}
cout<<maxx-1;
}