Manacher算法是一个求字符串的最长回文子串一种非常高效的方法,其时间复杂度为O(n)。下面分析以下其实行原理及代码:
1.首先对字符串进行预处理
因为回文分为奇回文和偶回文,分类处理比较麻烦,所以我们先要做一个预处理,在字符之间插入一个特殊字符(注意这个新插入的字符不能再原字符串中出现),这样无论原字符串是奇是偶,我们将它统一的转化为奇串。初此之外,我们还需在最开头也加一个特殊字符为了防止越界,例如:
原字符串 s =” abbaTNTabcba ” ,则经过转化后这个字符串会转变为:
sNew= “$#a#b#b#a#T#N#T#a#b#c#b#a#”
2.引入一个和处理过后长=长度相同的数组记录
引入一个数组假设计为p[snew.length()],其中 p[i] 表示以snew[i]为中心,半径为p[i]的最长回文子串,p[i]=1 则表示该回文子串就是senw[i]本身,下面是snew的最长回文子串半径:
由上图可知,snew[20]='c' 为中心的最长回文子串半径为6,由于第一个和最后一个字符都是#号,且也需要搜索回文,为了防止越界,由于字符串在结尾有’ ’,所以在字符串开头需要加上非#号字符(为了区分这里用的$)。通过p数组可以找到最大回文子串半径的最大值及其中心位置,就能确定最长回文子串了。所以现在问题转化为求p数组。
3.p数组(最长回文子串半径数组)的求法
Manacher算法利用开头提到的回文的左边是右边的镜像,让回文串起始的对比位置尽可能的大:
这里引入了两个新的变量id和mx,id为最大回文串中心的位置,mx为最大回文串的右边界,i为当前遍历带字符串的为位置。
这里分两种讨论:
一、mx > i
假设当前遍历到字符串的位置i,由于在遍历到id位置的时候已知最大回文子串,位置i还在上一个最大回文子串的范围内,所以可以利用其镜像认为,位置i以id为中心镜像到另一边的位置j是对等的。 在mx>i的条件下,又分为以下两种情况:
<1.mx - i > p[j] (图1)
此时,以j为中心的回文子串包含在以id为中心的回文子串内,由于i和j位置对等,所以以i为中心的回文子串包含在以id为中心的回文子串内,所以p[i] = p[j] = p[2 * id - i]。
<2. mx - i <= p[j] (图2)
此时,以j为中心的回文子串超过了以id为中心的回文子串边界,但是由于i和j位置对等,绿框部分还是相同的。所以其向右延伸的范围最大就是mx-i,剩下超过的部分谁也不能保证是否一致,只能通过循环对比判断,所以p[i] = mx - i。
二、mx < i
此时镜像位置对预判位置起不到作用,只能从长度为1进行对比,所以此时p[i]=1;
下面为实现代码:
#include<string>
#include <vector>
#include<iostream>
#include <algorithm>
using namespace std;
string Manacher(string s)
{
string snew="$#"; //预处理,只要在原字符串中不可能出现即可
int len=s.length();
for (int i=0;i<len;i++)
{
snew+=s[i];
snew+="#";
}
int len2=snew.length();
int lenans=-1; // 最长回文子串的长度
int pos=-1; // 最长回文子串中心点的位置
vector<int> p(len2, 0);
int id=0; //当前中心点的位置
int mx=0; //最大回文子串的右边界
for (int i=1;i<len2;i++)
{
if (i<mx)
p[i]=min(p[2*id-i],mx-i);
else
p[i]=1;
while(snew[i-p[i]]==snew[i+p[i]]) //最左边sNew[0]='$',最右边sNew[sNew.size()] = ' ',无需判断边界
p[i]++;
if(p[i]+i>mx)
//我们每走一步i,都要和mx比较,我们希望mx尽可能的远,这样才能更有机会执行if(i<mx)这句代码,从而提高效率
{
id=i;
mx=i+p[i];
}
if (p[i]-1>lenans)
{
lenans=p[i]-1;
pos=i;
}
}
string::iterator iStart=s.begin()+(pos-lenans-1)/2;
//将最长回文子串起始位置转换回原串
return string(iStart,iStart+lenans);
//也可以return lenans,最长回文子串的长度
}
int main()
{
string s;
cin >> s;
cout << s << " 的最长回文子串为: " << Manacher(s) << endl;
return 0;
}