Manacher算法:避免从1开始

问题引出


给定一个字符串str, 返回str中最长回文子串的长度

str=“123”。其中的最长回文子串“1”或者“2”或者“3”,所以返回1。
str=“abc1234321ab”。其中的最长回文子串“1234321”,所以返回7。
如果str的长度为N,解决原问题的时间复杂度都达到O(N).

输入描述:
输入为一个字符串str

输出描述:
输出一个整数表示最长回文子串的长度

此类问题与KMP算法类似,都是字符串匹配算法,KMP算法利用模式串的next数组,避免重复计算,其核心思想是:

当匹配失败时,下一个开始匹配点不是i+1和j=0,而是:j=next[j-1]

本问题思路

关于回文串,其有两大类类型:

  • abcba 即关于中间一个字符对称
  • abba 即关于中间两个字符对称

预处理:每一个字符两侧添加’#'字符

abba => #a#b#b#a#

计算的数组为dp[] 其中dp[i]表示第i个字符,向两侧最大可延长长度(当不匹配时不再延长)
可以证明:

dp[]中最大的值,即为原先字符串最大回文串长度

“#1#2#1#” 最大的dp值是以’2’为中心展开即: dp[3]=3 而这正是 “121” 最大回文串长度
"#1#2#2#1#"最大的dp值是以’#'为中心展开即:dp[4]=4 而这正是"1221"最大回文串长度

由此可见,采取预处理,可以使得原先问题转化为处理后字符串延长数组dp的求解。

O(N2)思路

即常规思路,研究每一个位置i 计算dp[i] 那么从i位置不断判断两侧是否相等:s[i-k]==s[i+k]?
该方法时间复杂度为平方级别。

O(N)思路

假设max_right是当前过程,最右边的延长触及位置,其含义是某一个点center,向外拓展最右边到达处

那么,当计算dp[i]时,如果i<max_right 即当前计算位置i包含于之前某个对称区间内,Manacher研究发现,可以利用之前的计算结果:
避免从i-1 i+1两侧开始延长,而是从某个最小的延长位置开始。

在这里插入图片描述

最小的延长对比长度为: min(dp[mirror],max_right-i)

证明如下:

①如果其关于center对称位置:dp[mirror]<max_right-i =》那么dp[i]一定等于dp[mirrot]

②如果其关于center对称位置:dp[mirror]>max_right-i =》 那么dp[i]一定大于等于max_right-i
反证法:如果dp[i]小于max_right-i 那么center所在的区间不可能最长延长到max_right处

由此可见,i如果在max_right内部,可以初始化为一个初始值。

如果为②情况,需要继续以min值开始向外延长比对,直到计算出dp[i]。

每一轮计算完dp[i],更新center,max_right 计算下是否为max_ans

代码实现

#include<string>
#include<vector>
#include<iostream>
using namespace std;
int main()
{
    
    string str;
    cin>>str;
    string s;
    s+='#';
    for(int i=0;i<str.size();i++)
    {
        s+=str[i];
        s+='#';
    }
    int len=s.size();  //初始化后的长度
    vector<int> dp(len,0);   ///dp[i]表示第i个字符 的最大延长  长度
    int center=0;
    int max_right=0;
    int ans=0;
    for(int i=0;i<len;i++)
    {
        if(i<max_right)
            dp[i]=min(dp[2*center-i],max_right-i);   //在max-right内部,可以避免从1开始延长
        else
            dp[i]=1; 
        //不管①② 这里可以统一延长计算 
        while(i-dp[i]>=0 && i+dp[i]<len && s[i-dp[i]]==s[i+dp[i]])  //延生法
        {
            dp[i]++;
        }
         if(i+dp[i]>max_right)
         {
             max_right=i+dp[i];
             center=i;               //重新变更拓展点
         }
        ans=max(ans,dp[i]-1);
    }
    cout<<ans<<endl;    
    
}

复杂度分析,由于每个位置i的计算,如果发生延长,那么将预先计算好临近区域,即对于每一个位置只计算一次,总的时间复杂度:O(N)

易错点:向外延长时,需要判断下标不越界。或者采取头部辅助一个’@'字符使得计算不会出界。

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页