【字符串】manacher 最长回文子串

【解题思路】求一个字符串的最长回文子串,如果暴力,每一个字符向他两端展开看是否相同,遍历整个数组便可以找到最长回文子串,但麻烦在还得分长度奇偶来考虑,比如”noon“,“level”是两种情况,并且时间复杂度很高,为O(n*n)。

Manacher‘s Algorithm将求最长回文子串的时间复杂度提高到了线性,这是非常了不起的。具体怎么操作我们一起来看!

第一步是预处理,给每一个字符前后两端都加上#

比如                   aba-->#a#b#a#  

                      noon-->#n#o#o#n#

这样做的好处在于长度为奇偶的串都变成了一种情况,求每一个字符向两端展开为回文半径,这个半径减去1就是本来回文串的长度,比如我们看#a#b#a#这个b在这个串里的最长半径为4,而aba的长度为3,符合。

再看#n#o#o#n#最中间的#半径为5,而noon的长度为4,也符合。

开一个p数组用来存放半径长度。

这样就解决了奇偶不同的问题,现在再看manacher的核心部分。

先上这张经典图 :      

mx为能延伸到最右端的位置,id是他的中点位置。

p[I]=mx-I?min(p[2*id-I],mx-I):1;

当mx>i时,p[i]就为p[2*id-i]和mx-i中小的那个,为什么是这样呢?看图,2*id-i也就是i关于id的对称点,要理解它为什么是取小的那个,1.如果mx-i大于p[2*id-i],p[2*id-i]是j的回文半径,如果这个半径小于mx-i,又因为j和i对称,p[i]=p[j];

2.如果mx-i小于p[2*d-i],发现mx管不住所有原来关于j对称的,所以只能取到mx-i,后面的怎么办呢 ?

我们加个循环判断一下即可!

while (t[i - p[i]] == t[i + p[i]])++p[i];

从这个半径开始向两边延伸,如果是回文,半径加一。

然后就是要维护这个mx使之成为能延伸到最右端的位置。

if (mx < i + p[i])
		{
			mx = i + p[i];
			id = i;
		}

完整代码如下:

//manacher 求最长回文子串长度
#include<iostream>
#include<vector>
#include<string>
#include<algorithm>
#include<cstdio>
using namespace std;
char a[110010];
int manacher(string s)
{
	int lens = s.size();
	string t = "$#";
	for (int i = 0;i < lens;i++)
	{
		t += s[i];
		t += "#";
	}
	int lent = t.size();
	vector<int>p(lent, 0);
	int mx = 0, len = 0, id = 0, center = 0;
	for (int i = 0;i < lent;i++)
	{
		p[i] = mx - i ? min(p[2 * id - i], mx - i) : 1;
		while (t[i - p[i]] == t[i + p[i]])++p[i];
		if (mx < i + p[i])
		{
			mx = i + p[i];
			id = i;
		}
		if (len < p[i])
		{
			len = p[i];
			center = i;
		}
	}
	return len - 1;
}
int main()
{
	
	while (scanf("%s",a)!=EOF)
	{
		printf("%d\n", manacher(a));
	}
	return 0;
}

猜你喜欢

转载自blog.csdn.net/weixin_43719720/article/details/90141877