马拉车算法(Manacher's Algorithm)

背景介绍:

给出一个字符串,求最长的回文字串,如果暴力的话,就是遍历所有点,以每个点当作中心向两边拓展寻找。这样的话,时间复杂度为O(n2);如果字符串长度很大的话,暴力的时间就有点长。这个时候Manacher 算法应运而生,也就是Manacher’s Algorith,俗称为马拉车算法。它的时间复杂度为O(n)。虽然作用比较单一,但还是掌握的为好。

算法过程:

一,插入#字符
为了能够将字符串统一起来,也就是让字符串的个数全都为奇数情况,马拉车算法提供了一种巧妙的方法:在每两个字符之间插入分隔符(包括首尾两端),分隔符一定为字符串中没有出现过的,比如#等字符。这样的话,如果字符串为偶数,则需要添加奇数个字符,二者相加为奇数,如果字符串为奇数,需要添加偶数个字符,相加还是为奇数。

eg:
abc
#a#b#c#

二,插入 $ 字符
为了能够让算法不会有越界情况发生,还要再字符串首尾加上另一种相当于用来结束的字符如$等,这样就保证了匹配到头或者尾的时候一定是不相等,就不会有越界了。当然对于尾部来说也可以不添加 $ 字符,因为字符串的结尾是通过’\0‘来实现的。上面保证了字符串数量一定为奇数,在这里加上两个字符后还是为奇数。

eg:
abc
$#a#b#c#

三,数组p的计算:
马拉车算法之所以能够在O(n)时间内完成,就是因为它巧妙地应用了回文串的特性,什么特性呢?举个栗子啊,比如aaaaaaa,如果暴力来做的话,会发生多个回文串重叠问题,也就是会重复计算多次,马拉车算法通过一个p数组来避免这种情况发生。
那么p数组这么的好,它里面存的是什么呢?其实p数组内部存的就是当前位最长的回文字串的半径,对于最长的回文字串长度,只要遍历p数组找到最大值,然后减一就是了。减一的原因是去掉多出来的’#‘字符。
eg:
在这里插入图片描述
接下来进入正题,如何求p数组呢?
从左向右的计算p数组,有一个mi为当前最长的字符串的中心。R为mi的半径,也就是最长能到达右端的长度。
计算p数组会有两种情况:
(1)i<=R情况,如何计算p[i]的值?因为我们的i是关于mi对称的一个点并且其与mi的距离不超过mi的半径R,所以一定能找到一个与i对称的点j在mi的左面,也就是2*mi-i的位置。那么接下来又可以分为两种情况。
a),p[j] < R-i。什么意思呢?这也就是说明了以点j为中心的回文串没有超出当前最长的回文串mi,那么根据字符串的对称性,我们就能得出p[i]=p[j],如图所示:
在这里插入图片描述

b), p[j] >= R-i。这也就是说明了以点j为中心的回文串超出了当前最长的回文串mi,这个时候对于i回文串和j回文串来说,并不一定能够总使得p[i] = p[j]。但根据回文串性质,一定可以知道至少p[i] = R-i的,至于能不能继续扩大,就得根据后续操作来判断了。如图所示:
在这里插入图片描述

(2)当i>R的时候。这个时候是没有办法根据回文串性质去简化的,只能一步一步的去匹配。注意在计算的过程中,要不断的更新mi的值。

代码:

int Manacher(string s)
{
	string res = "$#";
	
	for (int i=0; i<s.size(); i++)
	{
		res+=s[i];
		res+="#";
	}
	
	vector<int> p(res.size(), 0);
	int mi = 0, right = 0;
	int maxLen = 0, maxPoint = 0;
	
	for (int i=1; i<res.size(); i++)
	{
		p[i] = right>i ? min(p[2*mi-i], right-i) : 1;  //核心内容
		
		while (res[i+p[i]] == res[i-p[i]])
		{
			p[i]++; 
		}
		//更新right和mi的值
		if (right < i+p[i])
		{
			right = i+p[i];
			mi = i;
		}
		
		if (maxLen < p[i])
		{
			maxLen = p[i];
			maxPoint = i;
		}
	}
	
	return maxLen-1;
}

推荐例题:
poj3974:
地址:http://poj.org/problem?id=3974
一道模板题,没有啥好讲的。

AC代码:

#include <iostream>
#include <string>
#include <vector>

using namespace std;

int Manacher(string s)
{
	string res = "$#";
	
	for (int i=0; i<s.size(); i++)
	{
		res+=s[i];
		res+="#";
	}
	
	vector<int> p(res.size(), 0);
	int mi = 0, right = 0;
	int maxLen = 0, maxPoint = 0;
	
	for (int i=1; i<res.size(); i++)
	{
		p[i] = right>i ? min(p[2*mi-i], right-i) : 1;
		
		while (res[i+p[i]] == res[i-p[i]])
		{
			p[i]++; 
		}
		
		if (right < i+p[i])
		{
			right = i+p[i];
			mi = i;
		}
		
		if (maxLen < p[i])
		{
			maxLen = p[i];
			maxPoint = i;
		}
	}
	
	return maxLen-1;
}

int main()
{
	ios::sync_with_stdio(false);
	
	int t = 1;
	string s;
	
	while (cin >> s && s!="END")
	{
		cout << "Case " << t++ << ": " << Manacher(s) << endl;
	}
	
	return 0;
} 
  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值