扩展KMP

前言

扩展KMP又称Z函数,可以快速的求出一个字符串的每一个后缀的与其的LCP(最大公共前缀)长度。

至于为什么要学习exKMP,因为(数据规模很上进)我们都是上进的OIer。

算法思路

暴力朴素的算法

n个字符的字符串S中第i位开始的后缀与S的开头一一比较,求出LCP数组Z。

CODE

for(int i=1;i<n;i++)
{
    while(z[i]+i<n&&s[z[i]]==s[z[i]+i]) z[i]++;
}

由于上述算法太朴实无华了,所以我们的时间复杂度也很朴实无华达到O(n2)

exKMP

为了解决上述过分朴实无华的时间复杂度,exKMP学会了剥削利用一切可以利用的数据。

在exKMP中,我们从1n1依次计算z数组,那么我们在计算z[i]z[1,i1]是已经计算好了的。我们在计算过程中维护一个r,使r=max(z[j]+j1)(j<i),同时使l等于这个区间的左端点,即j。(r,l,j如图)

由z数值的定义可知,S[l,r]段是从第l位开始的字符串S的后缀与字符串S的LCP。那么S[0,z[l]1]=S[l,r]。因为r=z[l]+l1,所以S[0,rl]=S[l,r],且S[z[l]]!=S[r+1](如果等于,那么z[l]就不是最大的)。(如图)

举个例子:
S={aaabb}(i=0,4),当l=1时,z[l]=2,r=2。可以看出S[0,rl]=S[l,r]

理解上面后,我们来计算z[i]

1.若l<ir,则S[i,r]=S[il,rl](上一个图所述情况可以看成i=l的情况,i每往后挪动一位,il也往后挪动一位)。

1.1若z[il]<rl+1,也就是如图蓝线所示部分:

因为S[il,rl]=S[i,r],所以S[il,il+z[il]1]=S[i,i+z[il]1]。即上图中蓝红线所示部分相同。

而且S[il+z[il]]S[i+z[il]](上文有类似证明),所以z[i]=z[il+1]

1.2若z[il]ri+1时,使z[i]=ri+1,然后使用朴素的对比方法。(优质的食材往往使用最朴素的烹饪方法)(r以外的字符不清楚,那么就暴力查询)

2.若i>r,朴素求z[i]

注意每次求出z[i]后都要看当前的z[i]是否可以更新l和r。

代码:

for(int i=1,l=0,r=0;i<m;i++)
{
    if(i<=r&&z[i-l]<r-i+1) z[i]=z[i-l];//1.1的情况
    else
    {
        z[i]=max(0,r-i+1);//1.2或2的情况对z[i]的更新
        while(i+z[i]<n&&b[z[i]]==b[i+z[i]]) z[i]++;//暴力求法
    }
    if(i+z[i]-1>r) l=i,r=i+z[i]-1;//更新l,r
}

exKMP的时间复杂度

虽然我们看似进行了很多次的朴素方法,但是我们使用暴力求法时都是i+z[il]ri>r的时候,而且我们的r是不断向后变化的(不变化就一定是O(1)的解法),也就是说实际上我们只对S[](也就是代码中的b[])只遍历了一次(对于整个exKMP来说,下同),而外围循环求z也只会遍历一次z。所以总时间复杂度O(n)

后记

如果是两个字符串要求z函数,我们把他们拼成一条字符串,并且在连接处增加一个不属于这两个字符串的字符,就可以回归上述做法。

posted @   彬彬冰激凌  阅读(9)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
历史上的今天:
2023-10-24 2023NOIP A层联测16 T3 货物运输
点击右上角即可分享
微信分享提示