题意:给出两个字符串,然你判断第一个字符串在第二个字符串中出现的次数。
思路:一个裸的KMP算法。主要内容在下面代码中实现。
#include <iostream>
#include <cstring>
#include <algorithm>
#include <cstdio>
#include <string>
using namespace std;
int n;
const int maxn = 1e4 + 5;
char a[maxn * 100], s[maxn];//a是待匹配字符,s是需要查找的内容
int Next[maxn];//Next[i]表示前i位的前后缀匹配位数,字符串从下标0开始
void init() {
memset(Next, 0, sizeof(Next));
int len=strlen(s);
for(int i = 1; i <len; i++) {
int j = Next[i - 1];//根据KMP算法,当前Next的大小是根据前一位而来的,分为3种情况
//一是j==0,二是j!=0且s[i]!=s[j],三是j!=0但是s[i]==s[j]
while(j && s[i] != s[j]) j = Next[j - 1];//如果前一位不为1且不匹配,那么就回溯,就是第2种情况
Next[i] = (s[i] == s[j]) ? j + 1 : 0;//这里包括了第一种和第三种情况
}
}
int solve() {
int ans = 0;//这是子串数量
int j = 0;//当前位于s[j]的位置
int len1=strlen(a);
int len2=strlen(s);
for(int i = 0; i < len1; i++) {
while(j && a[i] != s[j]) j = Next[j - 1];//如果当前匹配失败,寻找是否符合的前后缀
if(a[i] == s[j])j++;//如果当前能够匹配,就j++,尝试下一个s的字符
if(j == len2) {
ans++ ;
//好像不需要? j = Next[j - 1];//当完成匹配的时候需要将j回溯,感谢大佬的纠错
} //如果已经位于s的最后,说明成功完成匹配一次
}
return ans;
}
int main() {
scanf("%d", &n);
while(n--) {
scanf("%s%s", s, a);
init();
printf("%d\n", solve());
}
return 0;
}
————————————————————————————————————————
2018年4月22日更新
这两天学了一下字符串哈希,于是重做了此题,下面贴上代码:
#include <cstring>
#include <iostream>
#include <map>
#include <cstdio>
#include <algorithm>
using namespace std;
typedef unsigned long long ull;
const int maxn = 1e7 + 5;
const ull has = 2333;
char s1[maxn], s2[maxn];
int Hash() {
int len1 = strlen(s1+1);
int len2 = strlen(s2+1);
int cnt = 0;
ull ll = 1;
ull a1 = 0;
ull a2 = 0;
for(int i = 1; i <= len1; ++i) {
ll *= has;
a1 = a1 * has + s1[i] ;
a2 = a2 * has + s2[i] ;
}
if(a1 == a2) cnt++;
for(int i = len1+1 ; i <= len2; ++i) {
a2 = a2 * has + s2[i] - ll * (s2[i - len1] );
if(a1 == a2) cnt++;
}
return cnt;
}
int t;
int main() {
scanf("%d", &t);
while(t--) {
scanf("%s%s", s1+1, s2+1);
printf("%d\n", Hash());
}
return 0;
}