最长回文子串问题-Manacher算法分析

算法思想

暴力法

对最长回文子串问题,首先明确回文子串的类型即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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值