Manacher算法

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

 

 

好了好了,大概就是这样了,以后可能会不定期更新,但是我是一个很懒很懒很懒的人,这篇博客我大概写了两个晚上吧(小声说:各种磨叽),还有那个图超级良心,是我自己亲手画的!!!!!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值