算法思想
暴力法
对最长回文子串问题,首先明确回文子串的类型即abba类型的偶数型子串和ababa型的奇数型子串,用暴力算法时,直接对两种子串进行分情况暴力跑。即对坐标为i的点,跑以left为i,right为i+1做起始状态的偶数型子串情况和以left为i-1,right为i+1做起始状态的奇数型子串情况。显然此复杂度为O(n^2)。
#include <bits/stdc++.h>
using namespace std;
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
string ss;
cin>>ss;
int sum=0;
for(int i=0;i<ss.size();i++)
{
int l=i-1,r=i+1;
int num=1;
while(l>=0&&r<ss.size()&&ss[l]==ss[r]){num+=2;l--;r++;}
if(num>sum)sum=num;
l=i,r=i+1;
num=0;
while(l>=0&&r<ss.size()&&ss[l]==ss[r]){num+=2;l--;r++;}
if(num>sum)sum=num;
}
cout<<sum<<endl;
}
return 0;
}
Manacher算法
对于字符串过长的情况,显然暴力法就无法实现了,而Manacher算法是实现O(N)复杂度查询最长回文子串的算法,它的思想主要是通过回文子串的性质(即以中心点左右对称)来通过前面的点算后面的点。
首先我们需要定义几个概念,R为当前遍历过的点中,回文串最右端最靠右的坐标(最右端坐标),C为R对应的那个中心点,P[length]数组作为i点回文串的半径(从中心点到其中一端的距离)。从暴力法就可以看出,在对字符串进行遍历时,当前的i点就是作为中心点来对左右进行遍历的。因此可以在每次求出回文串时存储一个P[i],若i+P[i]-1大于R,即更新R和C。
同时还要解决一个问题,回文子串的奇数类型和偶数类型,可以在字符串首尾和每个字符之间插入一个不会出现的值,例如’$’。那么此时无论是哪种类型都变成了奇数类型的串。
了解了这些就可以看Manacher算法的求解过程,在初始时,将C和R都赋成-1,此时遍历的i为0,R<i时对当前遍历点进行暴力求解。后更新C和R。在之后求解中,假设当前遍历的点是i_1,若i_1大于R,则对i_1进行暴力求解,否则,i_1在R左,如下图
因为当前已只C和R,故可求出i_2为i_1关于C的对称点,因为i_2的回文串半径已经在之前的求解中求解过,故可以通过回文串对称的性质对i_1进行求解。
(1)若i_2的左部在C的回文子串内,即i_2回文串的左部在C回文串的左部,则可以之间对称到C右边的i_1。
(2)若i_2的左部在C的左部的左边,此时i_2的右端正好等于C的右端R,证明为,设C的左部L的左一位为K1,K1相对i_1的对称点为K2,K2相对C的对称点为K3,K3同时也是i_2在这个长度下的左端,K4是K3相对于i_2的右端。因为i_2的串为回文串,故K1=k2,C的串为回文串,故K2=k3,但C的回文串长度半径最大值已经求出为C到R,若K1=K4,则与C的回文串长度矛盾了。故K1!=K4,故K3!=K4,因此此时i_2串的值为i_2到R。
(3)若i_2的左部与C的左部相同,则可之间将i_1的半径赋予i_2,后因不知道后续是否依然可构成更长的回文串,可对i_2的回文串继续向后遍历。
算法复杂度分析
因为R是回文串最右端最靠右的坐标,故是不可能向左的,对在R左端的所有坐标都是可以根据以上的三点以O(1)的复杂度进行。对故整个的过程就是对R向右扩充的过程,从-1到N,故复杂度为O(N)。
例题
这一天,thematrix和往常一样在AABB的直播间学习英语,但是枯燥的英语学习让thematrix感到枯燥乏味,于是他想知道AABB说的英文句子里有没有回文子串(原字符串中连续的一段回文串)隐藏其中呢?
于是thematrix找到了AABB的经典语录,每句话都只由小写字母组成,他发现这些语录中确实包含许多回文子串,他想要知道这些经典语录中最长的回文子串的长度分别是多少?
输入格式:
第一行一个正整数 T (1≤T≤10^2),表示字符串的数量。
接下来T行,每行包含一个字符串, 字符串的长度小于2*10^5。
输出格式:
输出包含 T 行,每行包含一个正整数,表示字符串最长的回文子串的长度。
输入样例:
2
aaaa
abab
输出样例:
4
3
#include<bits/stdc++.h>
using namespace std;
int r,c;
int p[400005];
int main()
{
int t;
scanf("%d",&t);
while(t--)
{
string ss;
cin>>ss;
string ss1="";
for(int i=0;i<ss.size();i++)//预处理字符串
{
ss1+='#';
ss1+=ss[i];
}
ss1+='#';
memset(p,0,sizeof(p));//初始化半径数组和rc
r=-1;
c=-1;
for(int i=0;i<ss1.size();i++)
{
if(r<i)//处理r<当前坐标的情况
{
p[i]=1;
for(int j=i-1,k=i+1;j>=0,k<ss1.size();j--,k++)
{
if(ss1[j]==ss1[k])
{
p[i]+=1;
}
else break;
}
if(i+p[i]-1>r){
r=i+p[i]-1;
c=i;
}
}
else{//处理r大于当前坐标的情况
int i1=2*c-i;
if(i1-p[i1]+1>c-p[c]+1)
p[i]=p[i1];
else if(i1-p[i1]+1<c-p[c]+1)
p[i]=r-i+1;
else{
p[i]=r-i+1;
for(int j=r+1,j1=2*i-j;j<ss1.size(),j1>=0;j1--,j++)
{
if(ss1[j]==ss1[j1])p[i]++;
else break;
}
}
if(i+p[i]-1>r)
{
r=i+p[i]-1;
c=i;
}
}
}
int maxi,max1=0;
for(int i=0;i<ss1.size();i++)
if(p[i]>max1)
{
max1=p[i];
maxi=i;
}
if(ss1[maxi]=='#')
cout<<max1/2*2<<endl;
else
cout<<max1-1<<endl;
}
return 0;
}