字符串—马拉车算法(求最长回文串)

字符串—马拉车算法(求最长回文串)

作为一个字符串渣渣,对于我来说,看见此类问题总会忍不住虎躯一震。。。初次见到“求最长回文串”问题时,我脑子里无非蹦出两个想法:
1.用栈的结构来存储,然后出栈看是否满足回文串的条件(回文串定义相信大家都知道,在此就不啰嗦了);
2.看见最长,立马想到用动态规划的思想,写出状态转移方程,找出最优子结构。
其实这两个思路合并起来是否可以解决问题——答案是肯定的。但这样一来的时间复杂度和空间复杂度都是很大的(时间复杂度

文章目录

  • 字符串—马拉车算法(求最长回文串)
    • 引入—回想KMP算法
    • 进入正题—马拉车算法的思路
      • 半径数组
      • 半径数组的计算
    • 代码实现
    • 总结

引入—回想KMP算法

KMP算法作为《数据结构》——串一章中,最重要的一个算法,想必大部分人都不陌生。同样的,这也是字符串问题中一个经典的将时间复杂度大大优化的经典算法。
我们先来回顾一下,KMP算法的核心是什么——找出待匹配的子串的最长公共前缀,一次确立next数组,作为对位失败后下次移位的基准。
所以说,欲解决当前字符串与其他字符串的问题,须先从自身下手——这也正是马拉车算法的方法论。

进入正题—马拉车算法的思路

半径数组

首先我们都知道,回文串有两种情况:
(1)串长为基数:如abcba,该串以c为中心点作回文分界;
(2)串长为偶数:如abccba,中心点为两个字符的分界。
针对这两种情况,为实现统一,先在串中每个字符间(首尾端都要插入)插入一个不冲突的特殊符号作为,例如:
原串为abbba,插入一个特殊符号#后变为a#b#b#b#a#
这样做的目的是:插入后字符串长度肯定为奇数了(奇+偶=奇),这样做之后就肯定不用考虑串的奇偶性了
接下来我们与KMP的next数组一样,引入一个概念:半径数组
这个数组p[i]用于记录以s[i]为中心的最长回文串的半径(不包括p[i]本身)。当p[i]=0时,说明回文串是字符串本身。
比如,#a#b#的半径数组为{0,1,0,1,0}
仔细体会一下,最大半径是不是就是最大回文串的半径?
这时我们还需注意的一点是:回文串搜索时我们需避免越界的问题。因此还要在已经处理过一遍的字符串外头尾加上两个特殊符号,例如:
#a#b#变为$#a#b#*
至于为什么计算的是半径,在于为了方便计算边界。

半径数组的计算

传统的 O ( n 2 ) O(n^2) O(n2)的做法,是以中心店开始,挨个将半径扩张,这么一来时间复杂度就很高,因为既要遍历、又要比对;
而马拉车算法巧妙的借用了p[i]数组,一直不断更新两个变量:
(1)id:回文串的中心位置;
(2)mx:回文串的最后位置;
p[i]=min(mx-i,p[2 id-i])
想想,为什么p[i]会如此定义?
(1)当遍历时一直都符合回文,mx-i为最坏情况下p[i]的最大值;而2id-i则为,当mx-i>p[i]时,由于对称性,2id-i和i肯定是对称的,所以以s[i]为中心的回文子串必包含在以s[id]为中心的回文子串中。

代码实现

此处指给出核心代码。并没有使用p数组表示,但思路一致

const maxn=10005;
const int mx=0,id=0;
int min(int &x,int &y)
{
	if (x<y) return x;
	else return y;
}
int max(int &x,&y){
	if(x>y) return x;
	else return y;
}
int Manacher(char str[],int mx,int id)
{
	int len[maxn];
	len[0]=0;
	for(int i=1;i<len;i++){
		if(i<mx) Len[i]=min(mx-i,len[2*id-i]);
		else len[i]=1;//回文串本身
		while(str[i-len[i]]==str[i+len[i]]) len[i]++;
		if(len[i]+i>mx){//越界,则一直遍历
			mx=len[i]+i;
			id=i;
			sum=max(sum,len[i]);
		}
	}
	return (sum-1;
}

总结

马拉车算法与KMP算法类似,需要先对字符串内部自身进行比较,且设置一个辅助数组。虽然说相比传统的动态规划方法,节省了时间开销,但在半径数组的求值问题中也用到了dp思想。当然,半径数组的设置也不是必要的。还有一点须注意的是,由于回文串存在奇偶性,应先对字符串进行预处理。

你可能感兴趣的:(数据结构,算法)