马拉车(Manacher)算法

Manacher算法是查找一个字符串的最长回文子串的线性算法。

把长度为奇数的回文串和长度为偶数的回文串一起考虑,具体做法是,在原字符串的每个相邻两个字符中间插入一个分隔符,同时在首尾也要添加一个分隔符,分隔符的要求是不在原串中出现,一般情况下可以用#号。为了边界问题,我们在字符串起始位置加入两个不同的字符。

用一个辅助数组hw[i]表示以字符a[i]为中心的最长回文字串的最右字符到a[i]的长度
hw数组有一个性质,那就是hw[i]-1就是该回文子串在原字符串S中的长度。
**证明:**首先在转换得到的字符串a中,所有的回文字串的长度都为奇数,那么对于以T[i]为中心的最长回文字串,其长度就为2*hw[i]-1,T中所有的回文子串,其中分隔符的数量一定比其他字符的数量多1(结束的位置一定是‘#’),也就是有hw[i]个分隔符,剩下hw[i]-1个字符来自原字符串,所以该回文串在原字符串中的长度就为hw[i]-1。

设置一个maxr表示已经触及的最右字符位置,mid表示以maxr为终点的回文串的对称字符的位置
从左到右计算hw[i],
如果i < maxr
那么我们求出i关于mid的对称点j。由于一个回文串反过来还是一个回文串,所以以i为中心的回文串的长度至少和以j为中心的回文串一样。但是以i为中心的回文串可能会延伸到maxr之外,而大于maxr的部分我们还没有进行匹配,所以要从maxr+1位置开始一个一个进行匹配,直到发生失配,从而更新maxr和对应的mid以及hw[i]。
如果i > maxr
对于中点为i的回文串还一点都没有匹配,这个时候,就只能老老实实地一个一个匹配了,匹配完成后要更新maxr的位置和对应的mid以及hw[i]。

#include <iostream>
#include <string>
using namespace std;

//复杂度为O(n)

string a;
int hw[22000005];

void init()
{
	string res; 
	int len = a.length();
	res += '@';   //最开始加与末尾不对称的字符 
	for (int i = 0; i < len; i++)
	{
		res += '#';   //加'#'和原来的字符 
		res += a[i];
	}
	res += '#';
	res += '$';  //与开始的字符不对称 
	a = res;
} 

int manacher()
{
	int ans = 0;
	int maxr = -1,mid;
	int len = a.length();
	for (int i = 1; i < len - 1; i++)
	{
		if( i < maxr ) hw[i] = min(hw[(mid<<1)-i],maxr-i); 
		/*如果i < maxr, 
		1.如果最小值是hw[(mid<<1)-i],那么不会进入while中,说明以i为中心的回文串被mid的回文串完全包含
		2.如果最小值是maxr-i,说明以i为中心的回文串超过了maxr,这时以maxr为起点更新 
		*/
		else hw[i] = 1;   //否则默认初始值为1 
		while( a[i-hw[i]] == a[i+hw[i]] ) hw[i] ++;
		if( hw[i] + i > maxr )   //更新maxr和mid 
		{
			maxr = hw[i] + i;
			mid = i;
		}
		ans = max(ans,hw[i] - 1);
	}
	return ans;
}

int main()
{
	cin >> a;
	init();
	cout << manacher() << endl;
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值