manacher(马拉车)算法简单讲解

介绍:manacher是一种用来求字符串子串中最长回文长度,时间复杂度可以达到 O ( n ) O(n) O(n)级别。
下面先介绍暴力求解法。
思路
①求出字符串的所有子串,时间复杂度为 O ( n 2 ) O(n^2) O(n2)。设置指针 l e f t , r i g h t left,right leftright,分别表示子串的左右下标,在第一个for循环枚举出所有可能的 l e f t left left,在第二个for循环枚举出在 l e f t left left情况下,所有可能的 r i g h t right right
②判断字符串为回文字符串的方法,时间复杂度为 O ( n ) O(n) O(n)。把字符串翻转,与原来字符比较如果相同即为回文字符串。
源代码

bool IsPalindromes(string s,int,int);
void FindSubstring(string s)
{
     
	int left,right,maxn=1;
	for(left=1;left<s.length();left++)
	{
     
		for(right=left+1;right<s.length();right++)
		{
     
			if(IsPalindromes(s,left,right));
			{
     
				maxn=max(right-left+1,maxn);
			}
		}
	}
	
}			

前方高能!!
下面进入正文
原理:对于奇数长度的字符串,如 s = " a b c b a " s="abcba" s="abcba",肯定有一个中心位置,如 c c c,左右分别是对称的区域,如果从第0个开始遍历,就会有一个时时更新的最右回文字符。比如,从第一个a开始最右的回文字符是是 s [ 0 ] ( a ) s[0] (a) s[0](a),到c时最右字符是 s [ 4 ] ( a ) s[4](a) s[4](a)
可能有人会问了这样做有什么用,接下来我就介绍如何利用这个最右边界。
假设这个最右边界为 m a x n maxn maxn,这个最右边界的中心为 c e n t e r center center,当前位置的下标为 s e r v i a l servial servial
①如果 s e r v i a l servial servial位于这个最右边界内,即 s e r v i a l < m a x n servialservial<maxn,根据对称我们可以找到左边的字母,如前面字符串中 s [ 4 ] a s[4]a s[4]a对应 s [ 0 ] a s[0]a s[0]a
根据前面的字母我们可以知道该字母的回文长度,如果长度的一半 − 1 -1 1没有超过左边界,那么很明显右边字母的回文长度等于左边的那个,这里画图解释一下吧。
manacher(马拉车)算法简单讲解_第1张图片
如果超过了左边界,那么就是说左边字母有些字符是不包含在这个最右的回文字符中的,所以我们只能先确定一部分回文字符(最右的坐标减去 s e r v i a l ∗ 2 + 1 servial*2+1 servial2+1),而右边的无法确定。
manacher(马拉车)算法简单讲解_第2张图片
②如果 s e r v i a l servial servial本来就不在最右字符串里面,就可以先让它的回文长度为 1 1 1,然后开始向搜索。这个应该好理解,就不画图了,有不懂的dd我。
这两种进行更新时,如果超过了回文字符串的最右边界,记得更新边境。

如果你以为这样就玩了,那你就错了,如果是字符串是偶数个时,我们是找不到,所谓的 c e n t e r center center,所以我们在每一个字符中间插入一个字符串没有的字符如 ∗ * ,最后还在开头跟结尾插入另两个字符作为哨兵。因此具体的做法也要稍稍调整一下,我们只用纪录长度的一半就好,因为有一半是无关的字符 ∗ *
如我们把 a b a aba aba,变为 ∗ a ∗ b ∗ a ∗ *a*b*a* aba,长度由 3 3 3变到了 7 7 7,其实我们只要记录 b b b和后面的长度即可,为 4 4 4 4 − 1 4-1 41即为正确结果。
对于偶数个的如 a a aa aa,变为 ∗ a ∗ a ∗ *a*a* aa,记录一半的长度为 3 3 3 3 − 1 3-1 31 o k ! ok! ok!

最后分别给一下两部分的函数

int transform(char*ori)
{
     
	cur[0]='@';//哨兵
	int t,len=str(ori);
	for(t=1;t<=2*len;t+=2)
	{
     
		cur[t]='*';
		cur[t+1]=ori[t/2];
	}
	
	cur[2*len+1]='*';
	cur[2*len+2]='$';	//哨兵
	cur[2*len+3]='0';	//字符串结束的标志
	return 2*len+1;
}

int manacher(char*cur,int len)
{
     
	int maxn=0,center=0,ans=0;	
	for(int t=0;t<=len;t++)
	{
     
		if(t<maxn)
			Len[t]=min(maxn-t,2*Len[2*center-1];	//看不懂的可以在纸上画
		else Len[t]=1;
		while(cur[Len[t]+t]=cur[t-Len[t]])
			Len[t]++;
		if(t+Len[t]>maxn)
		{
     
			maxn=t+Len[t];
			center=t;
		}
		ans=max(ans,Len[t]);
	}
	
	return ans-1;	//由于补充字符的结果,在纸上画一下很容易看出来的

o k ! ok! ok搞定,我写博客的目的主要是为了巩固知识,所以有部分会借鉴一下。
部分引用于这。

你可能感兴趣的:(算法,字符串,字符串,算法)