一种快速求解最长回文字符串长度的算法

最近在做OJ时遇到一个题——求解一个字符串最长回文子串的长度,题目如下:

时间限制: 1000ms
单点时限: 1000ms
内存限制: 64MB

描述

   小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相帮助,在编程的学习道路上一同前进。

   这一天,他们遇到了一连串的字符串,于是小Hi就向小Ho提出了那个经典的问题:“小Ho,你能不能分别在这些字符串中找到它们每一个的最长回文子串呢?”

   小Ho奇怪的问道:“什么叫做最长回文子串呢?”

   小Hi回答道:“一个字符串中连续的一段就是这个字符串的子串,而回文串指的是12421这种从前往后读和从后往前读一模一样的字符串,所以最长回文子串的意思就是这个字符串中最长的身为回文串的子串啦~”

   小Ho道:“原来如此!那么我该怎么得到这些字符串呢?我又应该怎么告诉你我所计算出的最长回文子串呢?

   小Hi笑着说道:“这个很容易啦,你只需要写一个程序,先从标准输入读取一个整数N(N<=30),代表我给你的字符串的个数,然后接下来的就是我要给你的那N个字符串(字符串长度<=10^6)啦。而你要告诉我你的答案的话,只要将你计算出的最长回文子串的长度按照我给你的顺序依次输出到标准输出就可以了!你看这就是一个例子。”

样例输入
3
abababa
aaaabaa
acacdas
样例输出
7
5
3
刚上手做的时候没想太多,直接按中心字符向两边比较字符是否相等,然后分为字符子串为偶数个和奇数个两种情况考虑的方法,结果虽然是正确的,但是提示超时,效率过低,于是上网查找算法,发现了一个不错的方法——Manacher算法。时间复杂度大约是O(n),特分享之。

首先是分析自己算法浪费时间的地方:一是要分奇数和偶数两种情况考虑,二是没有利用前面查找工作得到的结果。而这两点正是Manacher算法的优越之处,下面开始分析:

首先是如何利用前面的查找结果的,Manacher算法实际上是利用了回文字符串的一个特性——对称性。

例如一个字符串:abababa    最长回文子串就是本身abababa,中心字符为第四个字符b,而实际上我们可以发现在考察以第六个字符b为中心字符的回文字符子串的时候,是与考察以第二个字符b为中心字符的回文字符子串的情况相同的,所以是可以不必重复算的,这种算法本质上是一种动态规划思想,当然这么做有个问题就是作为参照的中心字符(上文的第四个字符b)的回文子串范围没有这么大的时候,结果就错误了。

例如一个字符串:aabaabaabac  在考虑以第九个字符b为中心字符的回文字符子串的时候就不能和以与它对称的第三个字符为中心字符的回文子串最大长度相等,而是在回文子串最右范围内的部分。因此我们需要做的工作就是不断求取目前最右回文子串的右范围坐标maxid以及中心字符坐标id,而以当前坐标i为中心字符的最长回文子串的长度就是min(p[2*id-i],maxid-i)。我们在这里用一个整数组p存储。

这个方法还有一个问题就是必须回文字符串长度要是奇数才行,这个问题我上面也说过,而Manacher算法用一个很巧妙的方法解决了这个问题,就是将所有的字符串无论奇偶全部变为奇数个——在一个字符串开头结尾以及任意两个字符之间插入一个无用字符'#',

例如字符串"abab"——>"#a#b#a#b#",这样无论子串个数是奇是偶,就都变成了奇数个,问题也就解决了。

最后附上我的代码仅供参考:

#include
#include
#include
using namespace std;

#define LENGTH 10000000                     //数组大小

char str[LENGTH],newstr[2*LENGTH];
int p[2*LENGTH];

int min(int a,int b)
{
	return ai)
		{
			p[i]=min(p[2*id-i],maxid-i);
		}
		else
		{
			p[i]=1;
		}
		while ((newstr[i+p[i]]==newstr[i-p[i]])&&(i-p[i])>=0&&(i+p[i])maxid)
		{  
			maxid=p[i]+i;  
			id=i;  
		}  
		if (ans>n;
	while(n-->0)
	{
		cin>>str;
		l=strlen(str);
		Change(str,newstr,l);
		Manacher(newstr,l*2+1);
	}
	return 0;
}










你可能感兴趣的:(一种快速求解最长回文字符串长度的算法)