Manacher 算法讲解 O(N)复杂度的 最长回文子串求解

求解最长回文子串的方法很多,有几种常见的O(N^2)的最长回文子串求解方法,比如说枚举中心位置向两边扩展,动态规划等,大部分朋友应该都比较熟悉。

Manacher算法相比于上面两种方法,时间复杂度是O(N),空间复杂度也是O(N),可以说是快速求解决回文子串的利器。下面介绍这一算法的思想,以及在文末给与它的实现。

我们以字符串 "ACBCABBB"为例(只考虑长度为奇数的回文串,待会会说明为何这么做),这里引入回文半径的概念,回文半径是指以该字符为中心的回文串的半径,比如ABA,B字符的回文半径为2,Manacher算法的关键点是利用了之前的计算结果,(KMP算法同样也是巧妙的运用了之前的计算结果,才有了高效的匹配速度),
比如说我计算到了第三个字符,也就是B为中心的最长回文串是ACBCA,其回文半径为3,那么我计算以下一个字符C为中心的最长回文串时,因为C处在B的回文半径之内, 我们已经知道C关于B字符的对称位置的C‘的回文半径了,C‘的回文半径为1,被包裹在了B的回文半径内部,因此以C为中心的回文串就不用计算了,其回文半径必定也是为1。

那么当我计算下一个字符A的中心的回文串时,同样的做法,先看A是否被包含在B的回文半径之内,然而并没有....两者相交了,因此A字符必须一步步计算其回文半径。

还有一种情况,比如说字符串DBCBDBCBHAHA,在计算第七个字符C为中心的回文半径时,C被包裹在D的回文半径内,因此我们知道C关于D字符C‘的回文半径了,但是C‘的回文半径为3,B的回文半径并没有把C‘的回文串全部包含在内,因此C的回文半径还是得重新计算,但是这里依然存在着计算优化,我们知道C的回文半径至少为2,之后的计算可以从2开始计算(根据对称位置C‘的回文半径得来)

算法基本思想就是这样,实际计算过程中我需要创建3个变量,分别是rArr[ lenOfString ],index,iRadius,这三个变量的含义如下:

rArr数组是存储每一位字符的回文半径

index,存储的是当前具有最右位置的回文串的字符的下标(最右位置看下边的解释)

iRadius,存储的是index下标的字符的回文半径

关于最右位置的解释,index和iRadius是配套使用的,所谓的最右位置是指index+iRadus-1的值最大(这个值恰好是index下标的字符为中心的回文串的最右边的字符的下标),

最右位置是需要不断更新的,因为这就是Manacher算法就是需要它来判断当前字符是否需要被重新计算。

回到上面的问题来,回文串有两种情况,一种是奇数,一种是偶数,显然Manacher对于是奇数的情况很好处理,那么如果最长回文串是偶数该如何处理呢?答案就是让它变为奇数,是否有种数学的思想在里面,将一个问题转化为一个已经解决了的问题。

因此,我们需要对字符串进行预处理,预处理的方法就是给它填充某种特定字符,比如字符串ABBA,先预处理成Manacher字符串,我给它填充#号,就变成了#A#B#B#A#,这样就可以保证,不管原先的字符串的最长回文子串是奇数还是偶数,填充了字符后,都将变成一个奇数的回文子串了。(假设原先的回文子串长度为N,对于长度为N的字符串将会给它填充N+1个字符,则它的长度变成了2N+1,即变成了奇数)。

至此,Manacher算法介绍完了。下面是它的实现代码,仅供参考:

//Manacher
string getManacherString(string& str){    /*预处理字符串*/
	string res="#";
	int i,len=str.length();
	for(i=0;i<len;i++)
	{
		res+=str[i];
		res+="#";
	}
	return res;
}
int MaxBackSubString(string src){
	int i,len=src.length();
	if(len<2)
		return len;
	string  str=getManacherString(src);
	len=str.length();
	int max=0;
	int* pArr=new int[len];
	int index=0,pRadius=0;
	for(int i=0;i<len;i++){
		if(i>=index+pRadius-1){     /*如果当前字符未被包裹在最右位置之内(如相交时),则需要一步步计算其回文半径*/
			int temp=1;
			while(i+temp<len&&i-temp>=0){
				if(str[i+temp]==str[i-temp])
					temp++;
				else
					break;
			}
			index=i;
			pRadius=temp;
			pArr[i]=pRadius;
			if(pRadius>max)
				max=pRadius;
		}
		else
		{
			int otherPos=index-(i-index);
			if(otherPos-pArr[otherPos]+1>index-pRadius+1)   /*如果其对称位置被包裹在index字符的回文半径之内,则无需计算了*/
			{
				pArr[i]=pArr[otherPos];
				continue;
			}
			else
			{
				int temp=otherPos-(index-pRadius+1)+1;      /*获取当前字符已经匹配的回文半径,从该回文半径开始计算它的最终回文半径*/
				while(i-temp>=0&&i+temp<len){
					if(str[i-temp]==str[i+temp])
						temp++;
					else
						break;
				}
				pRadius=temp;
				index=i;
				pArr[i]=pRadius;
				if(pRadius>max)
					max=pRadius;
			}
		}
	}
	delete[] pArr;
	return (2*max-1)/2;   /*因为填充了特殊字符的原因*/
}

int main(int arg1,char** arg2){
	/*
	char str1[]="today is sunny and all is right";
	char str2[]=" al";
	char* res=strstr(str1,str2);
	if(res!=NULL)
		cout<<res<<endl;
	else
		cout<<"res is null"<<endl;
		*/

	string str="caccccacd";
	int len=MaxBackSubString(str);
	cout<<len<<endl;
	getchar();
}

若有疑问或是发现错误,欢迎留言。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-19 03:56:24

Manacher 算法讲解 O(N)复杂度的 最长回文子串求解的相关文章

hiho#1032 : 最长回文子串 (manacher算法O(n)时间求字符串的最长回文子串 )

#1032 : 最长回文子串 时间限制:1000ms 单点时限:1000ms 内存限制:64MB 描述 小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相帮助,在编程的学习道路上一同前进. 这一天,他们遇到了一连串的字符串,于是小Hi就向小Ho提出了那个经典的问题:"小Ho,你能不能分别在这些字符串中找到它们每一个的最长回文子串呢?" 小Ho奇怪的问道:"什么叫做最长回文子串呢?" 小Hi回答道:"一个字符串中连续的一

《算法竞赛入门经典》3.3最长回文子串

1 //例题3-4 2 /* 3 * 输入一个字符串,求出其中最长的回文子串.子串的含义是:在原串中连续出现的字符串片段. 4 *回文的含义是:正看着和倒看着相同,如abba和yyxyy.在判断时,应该忽略所有标点符号和空格 5 *且忽略大小写,但输出应保持原样(在回文串的首部和尾部不要输出多余字符).输入字符长度不超过5000 6 *且占据单独的一行.应该输出最长回文串,如果有多个,输出起始位置最靠左的. 7 *样例输入:Confuciuss say:Madam,I'm Adam. 8 *样例

Leetcode-最长回文子串(包含动态规划以及Manacher算法)

原文地址: https://www.cnblogs.com/mini-coconut/p/9074315.html 给定一个字符串 s,找到 s 中最长的回文子串.你可以假设 s 的最大长度为1000. 示例 1: 输入: "babad" 输出: "bab" 注意: "aba"也是一个有效答案. 示例 2: 输入: "cbbd" 输出: "bb" 自己的思路:求一个字符串的最长回文子串,我们可以将以每个字符

[hdu3068 最长回文]Manacher算法,O(N)求最长回文子串

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=3068 题意:求一个字符串的最长回文子串 思路: 枚举子串的两个端点,根据回文串的定义来判断其是否是回文串并更新答案,复杂度O(N3). 枚举回文串的对称轴i,以及回文半径r,由i和r可确定一个子串,然后暴力判断即可.复杂度O(N2). 在上一步的基础上,改进判断子串是否是回文串的算法.记fi(r)=(bool)以i为对称轴半径为r的子串是回文串,fi(r)的值域为{0, 1},显然fi(r)是关于r

Manacher&#39;s algorithm: 最长回文子串算法

Manacher 算法是时间.空间复杂度都为 O(n) 的解决 Longest palindromic substring(最长回文子串)的算法.回文串是中心对称的串,比如 'abcba'.'abccba'.那么最长回文子串顾名思义,就是求一个序列中的子串中,最长的回文串.本文最后用 Python 实现算法,为了方便理解,文中出现的数学式也采用 py 的记法. 在 leetcode 上用时间复杂度 O(n**2).空间复杂度 O(1) 的算法做完这道题之后,搜了一下发现有 O(n) 的算法.可惜

最长回文子串(Manacher算法)

回文字符串,想必大家不会不熟悉吧? 回文串会求的吧?暴力一遍O(n^2)很简单,但当字符长度很长时便会TLE,简单,hash+二分搞定,其复杂度约为O(nlogn), 而Manacher算法能够在线性的时间内处理出最长回文子串. 让我们来看道题:http://acm.hdu.edu.cn/showproblem.php?pid=3068 这个算法的巧妙之处,便是把奇数的回文串和偶数的回文串统一起来考虑了.这一点一直是在做回文串问题中时比较烦的地方.这个算法还有一个很好的地方就是充分利用了字符匹配

最长回文子串问题—Manacher算法

Manacher 算法(http://www.jianshu.com/p/799bc53d4e3d) 对于一个比较长的字符串,O(n^2)的时间复杂度是难以接受的.Can we do better?先来看看解法2存在的缺陷. 1)由于回文串长度的奇偶性造成了不同性质的对称轴位置,解法2要对两种情况分别处理: 2)很多子串被重复多次访问,造成较差的时间效率. 缺陷2)可以通过这个直观的小??体现: char: a b a b a i : 0 1 2 3 4 当i==1,和i==2时,左边的子串ab

【转】最长回文子串的O(n)的Manacher算法

Manacher算法 首先:大家都知道什么叫回文串吧,这个算法要解决的就是一个字符串中最长的回文子串有多长.这个算法可以在O(n)的时间复杂度内既线性时间复杂度的情况下,求出以每个字符为中心的最长回文有多长,    这个算法有一个很巧妙的地方,它把奇数的回文串和偶数的回文串统一起来考虑了.这一点一直是在做回文串问题中时比较烦的地方.这个算法还有一个很好的地方就是充分利用了字符匹配的特殊性,避免了大量不必要的重复匹配.    算法大致过程是这样.先在每两个相邻字符中间插入一个分隔符,当然这个分隔符

Leetcode 5. Longest Palindromic Substring(最长回文子串, Manacher算法)

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000. Example 1: Input: "babad" Output: "bab" Note: "aba" is also a valid answer. Example 2: Input: "cbbd"