HDU 1711 Number Sequence(KMP模板题)

题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1711

解题思路:

1.暴力匹配会超时。

对于aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab和ab这种串复杂度会有O(N*M)

 

2.所以学一下专门解决此类问题的KMP算法。

当时的板子:

#include<bits/stdc++.h>
const int N = 1e6+5;
using namespace std;
int nt[N];
char s[N];
char ss[N];
int KMP() ///求大串中有几个子串
{
    int len=strlen(s);
    int t=strlen(ss);
    nt[0]=-1;
    int cnt=0;
    ///构造next数组
    for (int i=0,j=-1; i<t; ){
        if (j==-1 || ss[i]==ss[j]){
            i++;
            j++;
            nt[i]=j;
        }
        else j=nt[j];
    }
    for (int i=0;i<t;i++) printf("nt[%d]=%d ",i,nt[i]); printf("\n");
    ///开始遍历大串找其中的小串
    for (int i=0,j=0; i<len; ){
        if (j==-1 || ss[j]==s[i]){
            i++;
            j++;
        }
        else j=nt[j];
        if (j==t){
            cnt++;
            j=0;
        }
    }
    return cnt;
}

int main()
{
    while (cin>>s){
        cin>>ss;
        cout<<KMP()<<endl;
    }
}

有一篇讲的非常通俗易懂的博客 : https://blog.csdn.net/starstar1992/article/details/54913261/

kmp的核心是求得next数组,next[j]表示大串i位置和小串j位置不匹配时j回溯的位置。

next数组实现了两点:

①i不用回溯

②让j尽可能移到有效位置

本质上我们可以求得next数组的原因是:当你在第j位不匹配时,小串0~j-1的字符都是已知的。因此就可以在已知的字符上做点文章。

我个人理解起来最困难的地方在于构造next数组。

不过看了大佬的博客后就在构建next数组处换了大佬的思考方式。

next[j] 表示 0~j-1前缀集合和后缀集合中相同且最长的长度。

For example,对于abbaaab,求next[4]

那么在abba中        

前缀集合:a,ab,abb

后缀集合:a,ba,bba

最长相同长度为“a”,所以next[4] = 1

将这种想法带入构造next数组过程。

每次构造成功一个next[i] = j时,表示ss[0]~ss[j-1] 和 ss[i-j]~ss[i-1]相同。

那么当ss[j] == ss[i]时证明next[i+1] = j+1,因为0~i的前缀集合和后缀集合中相同且最长的长度 最多就是比0~i-1多1。

如果不相同,那么j就会"不甘心"的回溯到next[j],如果next[j]不是-1,说明next[i+1]还是有可能大于0的。

这是一张手绘的表达当i和j位置不匹配时,j回溯到next[j]的情况的图:(我感觉精髓都在这儿了)

 

然后是本题的代码:

#include<cstdio>
#include<cstring>

const int N = 1e6+5;
const int M = 1e4+5;

int s[N],ss[M],nt[M],ans;

void kmp(int len,int len2)///分别表示大串长度,小串长度
{
    nt[0] = -1;
    for (int i=0,j=-1;i<len2;){
        if (j==-1||ss[i]==ss[j]){
            i++;
            j++;
            nt[i] = j;
        }
        else j = nt[j];
    }
    for (int i=0,j=0;i<len;){
        if (j==-1||s[i]==ss[j]){
            i++;
            j++;
        }
        else j = nt[j];
        if (j==len2){
            ans = i-len2 + 1;
            return ;
        }
    }
}

int main()
{
    int T,n,m;
    scanf("%d",&T);
    while (T--){
        scanf("%d %d",&n,&m);
        for (int i=0;i<n;i++) scanf("%d",s+i);
        for (int i=0;i<m;i++) scanf("%d",ss+i);
        ans = -1;
        kmp(n,m);
        printf("%d\n",ans);
    }
    return 0;
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值