Manacher算法——用于求取最长回文子串

  1. 回文串的概念:正序和逆序是一致的,比如字符串abcdcba
  2. 既然是字符串就会存在奇数长度和偶数长度的回文串
奇回文串:abcdcba
偶回文串:123321
  1. 实现方法:
    (1)暴力的扩展——时间复杂度O(n^2)
    遍历每一个字符,从这个字符开始向两边扩展并且比较是否一致,记录每个字符可以遍历到的最大长度。比如abcdcba,当遍历到字符d的时候就是最长的回文串。
    但是没有办法解决偶数长度的串,比如123321是没有办法从中间向两边扩展的,因为中轴是虚拟的,所以可以预先处理一下给定的字符串,让虚轴显现。
方法:在字符串的开头、两两字符之间、结尾都添加特殊字符#,当然也可以是串内字符,因为添加的字符只会和特殊字符进行匹配,都是关于自身来实现对称的
比如:
偶回文串:123321,	预处理后:#1#2#3#3#2#1#,这样就显现了虚轴
奇回文串:aba,		预处理后:#a#b#a#

这样从第一个字符需要比较1次,第二个2次,一直到中间,再对称,两个等差数列。时间复杂度就是一个O(n^2)级别的。

(2)Manacher算法,时间复杂度:O(n)

会用到的关键词:
1、回文半径/回文直径数组arr【】:记录从每个位置开始向两边扩展可以到达的最边界
2、最右回文边界pR:顾名思义就是回文串的最右边界,从0位置开始
3、最右回文半径的回文中心C:回文串的中心位置,取决于pR,当pR不动则不变,否则更新

情况共有四种,分为两类

1、pos位置的字符在最右回文边界pR的右边,只能直接暴力扩展

case1

2、pos位置的字符在最右回文边界pR的左边,分为三种情况:
	2.1、pos位置关于回文中心C的对称点pos’的回文半径彻底在回文直径(L,R)内

在这里插入图片描述

C是回文中心,由于已经扩展到(L,R)
∴	m = q
	n  = p
	但是pos'的回文区域(L',R')完全在(L,R)之间,
∴	m ≠ n 
则	p ≠ q	所以不需要扩再进行验证

========================================================

2.2、pos位置关于回文中心C的对称点pos’的回文半径部分在回文直径(L,R)外

在这里插入图片描述

C是回文中心,回文直径扩展到(L,R)
∴	q ≠ p 
	y = x 
而pos'的回文半径是(L',R')则
	q = y
∴	x ≠ p
∴	所以R-pos之后不需要扩再进行验证

========================================================

2.3、pos位置关于回文中心C的对称点pos’的回文半径恰好压线(左边界重合)

在这里插入图片描述

由图可知:
y = m
x ≠ n
x ≠ y
则x ≠ m 但是无法确定m和n的关系,所以需要继续验证

综上就是所有的情况

  1. 代码
/*
 *最右回文边界pR
 *回文中心C
 *回文半径数组pArr[]--记录从左向右扩pR的每一个位置回文半径
 */
/**
 *case1:字符在回文边界pR的右边--暴力扩
 *case2:i'的(区域)在L内--不扩
 *case3:i'的(区域)一部分在L内--不扩
 *case4:i'的(区域)压线--扩
 */
#include<bits/stdc++.h>
using namespace std;


vector<char> GetManacherString(char str[])
{
	int len = strlen(str);
	vector<char> res(len*2+1);
	int index = 0;
	for (int i = 0; i < res.size(); ++i)
	{
		res[i] = (i&1) == 0 ? '#' : str[index++];
	}
	return res;
}

//最长的回文子串

int MaxLcpsLength(char str[])
{
	int len = strlen(str);
	if(len == 0 || str == NULL)	return 0;
	std::vector<char> ManacherString = GetManacherString(str);
	int pR = -1;		//最右回文边界
	int C = -1;			//最右回文中心	
	int maxn = INT_MIN;	//最大回文半径
	int str_Center = -1;//最长回文串回文中心
	int ManacherString_len = ManacherString.size();
	std::vector<int> pArr(ManacherString_len,0);

	for (int i = 0; i < ManacherString_len; ++i)
	{
		//字符在pR的里面(case2、case3)
		pArr[i] = pR > i ? min(pArr[2*C-i],pR - i) : 1;				//获取对称点i'的pArr[i']
		while(i+pArr[i] < ManacherString_len && i - pArr[i] > -1) 	//越界检查
		{
			if(ManacherString[i+pArr[i]] == ManacherString[i-pArr[i]]){ //R>i case2和case3不进入 直接break
				pArr[i]++;
			}else{
				break;
			}
		}
		if(i+pArr[i] > pR){	//字符在pR的外面 case1
			pR = i+pArr[i];
			C = i;
		}
		if(pArr[i] > maxn)
		{
			maxn = pArr[i];
			str_Center = C;
		}
	}
	int Lastmaxn = maxn-1;
	//cout<<"Right Center is:"<<C<<endl;
    //cout<<"Center is:"<<str_Center<<endl;
    //求取子串的内容:如下利用函数substr((str_Center-maxn-1)/2, maxn);
	string newstr = str;
	cout<<"newstr is:"<<newstr<<endl;
	cout<<"MaxLcpString is:"<<newstr.substr((str_Center-Lastmaxn)/2, Lastmaxn)<<endl;
	return Lastmaxn;
}

int main(int argc, char const *argv[])
{
	char str[] = "abc1234321ab";
	cout<<"oldstr is:"<<str<<endl;
	cout<<"MaxLcpsLength is: "<<MaxLcpsLength(str)<<endl;
	return 0;
}

结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值