一、暴力做法
给定一个模式串 S,以及一个模板串 P,所有字符串中只包含大小写英文字母以及阿拉伯数字。
模板串 P 在模式串 S 中多次作为子串出现。
求出模板串 P 在模式串 S 中所有出现的位置的起始下标。
输入格式
第一行输入整数 N,表示字符串 P 的长度。
第二行输入字符串 P。
第三行输入整数 M,表示字符串 S 的长度。
第四行输入字符串 S。
输出格式
共一行,输出所有出现位置的起始下标(下标从 0 开始计数),整数之间用空格隔开。
数据范围
1≤N≤10^5
1≤M≤10^6
输入样例:
3
aba
5
ababa
输出样例:
0 2
for (int i = 1; i <= n; i ++)
{
bool flag = true;
for (int j = 1; j <= m; j ++)
if (s[i + j - 1] != p[j] )
{
flag = false;
break;
}
}
二、KMP思路
可以从暴力做法看出,若模式串s与模板串p匹配失败,则j ++,即模板串将从模式串的下一位开始匹配,我们将重新遍历一次模板串。但我们在前一次遍历的时候已经确定了部分模板串是与模式串匹配的,所以我们可以通过next数组,求出已匹配过的模板串中最大相等的前后缀(即若next数组长度为j,则p[1,j]==p[i - j + 1,i](假设i为已匹配的模板串的长度))
通俗点来讲,就是在模板串匹配的时候,若其下一点不匹配,则将从模板串前、后最长公共子缀的后公共子缀开始匹配(因为前公共子缀在这次匹配的时候已经通过匹配了,所以我们知道这段字符串是可以匹配成功的,直接跳过这段前公共子缀,从下一公共子缀的末尾开始匹配)
三、KMP代码
#include <iostream>
using namespace std;
const int N = 10010, M = 100010;
int n, m;
char p[N], s[M];
int ne[N];
int main()
{
cin >> n >> p + 1 >> m >> s + 1;//下标习惯从1开始,所以p和s的值都加了1
//求next的过程,本题难点,其实跟KMP匹配过程类似
for (int i = 2, j = 0; i <= n; i ++)
{
while (j && p[i] != p[j + 1]) j = ne[j];
if (p[i] == p[j + 1]) j ++;
ne[i] = j;
}
//KMP匹配过程
for (int i = 1, j = 0; i <= m; i ++)
{
while (j && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) j ++;
if (j == n)
{
printf("%d ",i - n);//若下标从1开始,为i - n + 1,题目要求下标从0开始
j = ne[j];//继续匹配
}
}
return 0;
}