扩展kmp算法

考虑如下的问题:

  • 给出一个长度 n 的字符串S0..n1
  • 和一个长度 m 的字符串T0..m1
  • S 的哪个后缀和T具有最长的公共前缀(Longest Common Prefix,以下简称LCP)

让我们来简单分析一下,暴力做法就是枚举 S 的每一个后缀和T匹配,时间复杂度是 Θ(nm)

这个做法中,主要是重复运算拖慢了速度,效率不高。

首先我们可以试着和kmp一样的思路:

先假设 T 的每一个后缀Ti..m1 T0..m1 本身的LCP用数组 nexti 存储,特别地, next0=m

然后再学习manacher算法的思想:设答案伸得最远的后缀(不包括原串)为 Tp..n1 ,如果其公共前缀没有包含字符 Ti ,就暴力求 nexti (当然, i=1 时也暴力求),否则可以像manacher一样直接拿现成结果。

如图所示(图示转自yhf4aspe的blog):

那么可以直接求出的部分就是 nextip
因为 Ti..p+nextp1=T0..nextp1
求不出的部分就可以从 u 开始暴力求。

代码实现:

next[0] = m;
for(i = 0; T[i] == T[i + 1]; i++);
next[1] = i;
p = 1;
for(i = 2; i < m; i++) {
    u = p + next[p]; //u为最右端位置
    if(i + next[i - p] < u) next[i] = next[i - p]; //如果答案可以直接取
    else { //如果答案需要探索
        for(j = max(u - i, 0); T[i + j] == T[j]; j++);
        next[i] = j;
        p = i;
    }
}

至于S T 部分也是类似的思路。

时间复杂度分析:

第一步求next时内层for循环每执行一次, p 至少伸长一格,因此内层for循环至多循环m次,加上外层for循环其余语句,可得时间复杂度 Θ(m)

第二步求ex时同理, Θ(n)

总的时间复杂度为 Θ(m+n)

扩展kmp算法的模板:

//扩展kmp by LKB 2016.8.7
#include <iostream>
#include <string>

using namespace std;

const int maxlen = 1e5;

string S, T;

int next[maxlen];
//next[i]储存T的每一个后缀T[i..n-1]和T[0..n-1]本身的最长公共前缀
int ex[maxlen];

int main() {
    cin >> S >> T;
    int n = S.size();
    int m = T.size();
    next[0] = m;

    for(int i = 0; i + 1 < m; i++) //预处理计算next[1]
        if(T[i] == T[i + 1]) ++next[1];
        else break;

    int p = 1; //答案伸得最远的后缀(不包括原串)为T[p..n-1]

    for(int i = 2; i < m; i++) {
        int u = p + next[p]; //u为最右端位置 

        if(i + next[i - p] < u) next[i] = next[i - p]; //如果答案可以直接取
        else { //如果答案需要探索
            int j;

            for(j = max(u - i, 0); T[i + j] == T[j]; j++);

            next[i] = j;
            p = i;
        }
    }

    for(int i = 0; i + 1 < n; i++)
        if(S[i] == T[i]) ++ex[0];
        else break;

    p = 0;

    for(int i = 1; i < n; i++) {
        int u = p + ex[p]; //u为最右端位置

        if(i + next[i - p] < u) ex[i] = next[i - p]; //如果答案可以直接取得
        else { //如果答案需要探索
            int j;

            for(j = max(u - i, 0); S[i + j] == T[j]; j++);

            ex[i] = j;
            p = i;
        }
    }

    int ans = 0;

    for(int i = 0; i < n; i++) ans = max(ans, ex[i]);

    cout << ans << endl;
    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值