题目链接: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;
}