Manacher算法
复杂度为O(n)的某种字符串算法
用法:大概就是用来求一个字符串中最长回文子串的长度
for example: abacada的最长回文字串的长度为3
关于求最长回文子串,其实还有另外两种方法
1.从左到右枚举每一个点,求以这个点为起点的最长回文子串 —— O(n^3)
2.从左到右枚举每一个点,求以这个点为中点的最长回文子串 —— O(n^2)
但是这两种时间复杂度都太高了,分分钟TLE
ok,以下进入正题
Manacher算法有三个主要的点
1.将偶回文奇回文全部转换为奇回文
2.有一个辅助数组p的存在,p[i]记录以i为中点时最长回文串的半径
3.有两个辅助变量,mid,maxright的存在,使得算法复杂度大大减小
怎么将偶回文和奇回文全部转为奇回文
这有个非常巧妙的方法,就是在最前面和最后面以及每两个字符之间加入一个符号,该符号要在原字符串中未出现过,比如说可以用#,大概很少会出现吧。
for example 原串为abcba 进行上述操作后变成#a#b#c#b#a#
对于为什么,如此操作之后会变成偶回文,开始证明:设原串的长度为n,那在两两字符串之间要插入n-1个#,再加上最前面和最后面,如此操作后,总共加上了n+1个#,加上原来有n个字符,所以此时有n+1+n = 2*n+1个字符,比为奇数, 得证!
辅助数组p
先明确p是干嘛的,p[i]记录以i为中点时最长回文串的半径
然后就会发现一件很有意思的事情
p[i]-1的值其实就是以i这个字符为中心的最长回文串的长度(在原串中)
来来来,我们继续for example,我觉得这解释方法最直观了
for example: #a#b#a#c#,i=4时,字符为b,p[i] = 4,而在原串abac中,以b为中心的最长回文串的长度为3=p[i]-1,回文串为aba
好累啊,我也是个很懒的人,嗯,我就不具体证了,相信你们一定可以自己领会的,加油!主要就是因为前面将原字符串扩大了一倍,然后又是奇回文,所以要-1
辅助变量mid,maxright
很多博客都是把mid命名为id,把maxright命名为mx,然后对于我来说,可能这样更好理解一点,在这里两种都写出来,看你们哪种好理解了,下面的具体解释中都会用mid和maxright
mid和maxright,mid是已经拓展过的点中,能到最右端的最长回文串的中点,maxright是该最长回文串的最右端点,由此可以发现关系,maxright = mid + p[mid]-1
mid是已经拓展过的点中,能到最右端的回文串的中点
Q:为什么要用到辅助变量mid和maxright呢
A:为了让复杂度降低,让manacher比枚举强
Q:怎么降低复杂度
重点来了!!!重点!!!!!
高能预警,因为我看了好久才反应过来,嗯,也有可能是因为我太蠢了吧
先上代码
if (i < mr)
p[i] = p[2*mid - i];
else p[i] = 1;
解释:
判断该点i是否在maxright的左边,如果maxright的右边的话,可以把它优化一下,也就是把p[i]的初始值设为p[j](j和i关于mid对称,关系式为j = 2*mid-i),以此可以减少时间复杂度,否则都从1开始拓展的话,时间复杂度会大大增加。
为什么可以把p[i]的初始值设为p[j]呢?利用到回文串的对称性
上图
几点说明(好吧,其实就一点):
1.i一定在mid的右边,因为mid是已经拓展过的点中,能到最右端的最长回文串的中点
那么会有一个问题,我们发现有一种情况我们没有考虑过,如图
这时我们会发现一件事情,如果i+p[i]-1超过maxright的话,不能保证超出的那部分与i的左边那部分可以构成回文(标黄的那段)也就是说我们只能保证i到maxright这段在i的左边有一段相同的,两段可以构成回文串
所以p的初始值应该是mr-i和p[2*mid - i]两者的较小值
正确代码
if (i < mr)
p[i] = min(mr-i, p[2*mid - i]);
else p[i] = 1;
然后我们会发现maxright和mid的值是随时有可能会变化的,所以每次都要判断过,确定maxright和mid是否需要更新
if (mr < p[i]+i)
{
mid = i;
mr = p[i]+i;
}
核心代码
核心代码附上
for (int i(1); i <= len_now; ++i)
{
if (i < mr)
p[i] = min(mr-i, p[2*mid - i]);
else p[i] = 1;
while (i-p[i] >= 0 && i+p[i] <= len_now)
{
if (ch[i-p[i]] != ch[i+p[i]]) break;
p[i]++;
}
if (mr < p[i]+i)
{
mid = i;
mr = p[i]+i;
}
maxlen = max(maxlen, p[i]-1);
}
相关变量说明,ch[]是存储处理过后的字符串,len_now是处理过后字符串的长度
关于时间复杂度
Manacher算法的时间复杂度是O(n)的,p[i]的初始值变化导致算法复杂度大大减小。
复杂度怎么算呢,我太懒了,没算
贴一个网址,他在最后有提到如何计算Manacher的复杂度 https://subetter.com/articles/manacher-algorithm.html
好了好了,大概就是这样了,以后可能会不定期更新,但是我是一个很懒很懒很懒的人,这篇博客我大概写了两个晚上吧(小声说:各种磨叽),还有那个图超级良心,是我自己亲手画的!!!!!