最长回文子串----Manacher(谐称"马拉车")算法详解

问题:

         给定一个字符串s,找到s中最长的回文子字符串。

         所谓回文字符串,指的是无论从左往右读还是从右往左读,结果都是一样的,也叫做对称字符串。

比如 “google” 的最长回文子串为 “goog”。

 

问题分析

        求解回文串的一般方法是首尾比较,以中点为界限,如果一直到中点首尾对应的字符都相等,则该字符串为回文串

例:设str="abccba",从i=0开始,str[0]==str[5],str[1]==str[4],str[2]==strs[3],i=2时到达中点,此前,首尾对应的字符都相等,所以该字符串为回文串。

        要想求一个字符串的最长回文字串,则需依次判断该字符串的所有字串,这样的算法时间复杂度为O(n^3),显然这样的算法并不好,本文介绍的算法可以将时间复杂度降为O(n),这大大降低了时间复杂度。算法的性能大大提升。

 

算法分析

        这里用一个例子来说明算法的过程。如 cabadabae,我已经知道了第三位为中心的aba和第5位为中心的abadaba是回文,

那么由回文的特性,就能够知道2-4位和6-8位对称,而又知道第3位为中心的aba是回文,所以2-4位是回文。这样的话,6-8位肯定是回文。(aba aba e)这样的话判断以第七位为中心的时候就不必再重新判断了(对称可得,第三位和第七位对称)。

       同理,在判断第六位为中心时,由于之前的计算已经知道了第5位为中心的cabadabae是回文,而第4位为中心的a的回文长度是1,所以第6位为中心的回文长度只能是1,不用再去扩展判断了。

      再分析以第七位为中心的情况,由之前分析已经知道当前最大长度是3了,但是还是需要进行扩展,因为第9位是什么根据之前的信息无法得知,需要扩展进行探索。

     总结:整个算法的核心是已知回文的右边界,如果以当前为中心的回文长度小于当前计算的最大长度,则不必再向右边扩展,反之则要扩展。

     步骤:

1、首先,我们要记录下目前已知的回文串能够覆盖到的最右边的地方,就像案例中的第8位

2、同时,覆盖到最右边的回文串所对应的回文中心也要记录,就像案例中的第5位

3、以每一位为中心的回文串的长度也要记录,后面进行推断的时候能用到,就像案例中用到的以第3位为中心的回文和第4位为中心的回文

4、对于新的中心,我们判断它是否在右边界内,若在,就计算它相对右边界回文中心的对称位置,从而得到一些信息,同时,如果该中心需要进行扩展,则继续扩展就行。

 

        最后讨论一点:回文串的长度,如果回文串的长度为偶数,回文中心是啥呢?这是就没有回文中心了。所以,还要对字符串进行预处理,在两个字符中间加入一个任意的特殊字符,包括首尾,只要能起到标识作用的都行,例如 '#' ,还是上面的例子,预处理之前为cabadabae,处理之后为#c#a#b#a#d#a#b#a#e#,这样就统一了回文串为基数和偶数两种情况

 

代码如下

#include
#include
using namespace std;

void Manacher_algorithm(string& str)
{
	for (int i = 0; i <= str.size(); i = i + 2)
	{
		str.insert(i, 1, '#');
	}
	int len = str.size();   //处理后字符串长度
	int rightside = 0;      //右边界
	int rightSidecenter = 0;//右边界对应回文串中心
	int *halflenArr = new int[len]();  //保存以每个字符为中心的回文串长度的一半
	int center = 0;         //记录回文中心
	int longestHalf = 0;    //记录最长回文长度
	bool needcalc;
	for (int i = 0; i < len; i++)
	{
		needcalc = true;
		if (rightside > i)  //如果在右边界的覆盖之内
		{
			int leftCenter = rightSidecenter * 2 - i;  // 计算相对rightSideCenter的对称位置
			halflenArr[i] = halflenArr[leftCenter];    //根据回文性质得到(对称)
			if (i + halflenArr[i] > rightside)      // 如果超过了右边界,进行调整
			{
				halflenArr[i] = rightside - i;
			}
			if (i + halflenArr[i] < rightside)   // 如果根据已知条件计算得出的最长回文小于右边界,则不需要扩展了
			{
				needcalc = false;
			}
		}
		if (needcalc)
		{
			while (i - 1 - halflenArr[i] >= 0 && i + 1 + halflenArr[i] < len)
			{
				if (str[i + 1 + halflenArr[i]] == str[i - 1 - halflenArr[i]])
				{
					halflenArr[i]++;
				}
				else
				{
					break;
				}
			}

			// 更新右边界及中心
			rightside = i + halflenArr[i];
			rightSidecenter = i;

			// 记录最长回文串
			if (halflenArr[i] > longestHalf)
			{
				center = i;
				longestHalf = halflenArr[i];
			}
		}
	}
	for (int i = center - longestHalf + 1; i <= center + longestHalf; i = i + 2)
	{
		cout << str[i];
	}
	cout << endl;
}

int main()
{
	string str;
	while (cin >> str)
	{
		Manacher_algorithm(str);
	}
	system("pause");
	return 0;
}

 

参考资料:

https://mp.weixin.qq.com/s/Zrj35DrnQKtAENiR5llrcw

 

 

你可能感兴趣的:(算法)