一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第8天,点击查看活动详情。
暴力算法
- 假设S[N]是要匹配的串,比较长的串
- P[M]模板串,比较短的串
- KMP习惯下标从1开始
- 做法
- 第一层循环,i是当前的起点,假设从s[i]开始匹配p能否匹配成功
- flag:用来判断是否匹配成功
- 第二层循环,j是p的起点
如何优化
- next[i]:表示以i为后缀,跟从1开始的前缀相等,而且后缀的长度最长(也就是匹配的长度最长)
- 例子:next[i] = j
- 含义:表示以i为后缀,跟从1开始的前缀相等,而且后缀的长度最长为j
- 也就是p[1, j] = p[i - j + 1, i]这两段长度相等
- 例子:next[i] = j
- 如何应用1.呢?
题目
代码
#include <iostream>
using namespace std;
const int N = 1e5 + 10, M = 1e6 + 10;
int n, m;
char p[N], s[M]; // p是短串 s是长串
int ne[N]; // next数组
int main()
{
cin >> n >> p + 1 >> m >> s + 1; // 下标从1开始
// 求next数组 i是从2开始的,因为next[1] = 0(全局数组默认值是0了),如果第一个失败的话,就回到起点了
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匹配过程
// 试图跟si匹配的是p[j + 1],所以j往前错一位
for (int i = 1, j = 0; i <= m; i++)
{
// 如果j没有退回起点,而且s跟p出现第一个不能匹配的点了
// 那么就将p的j退回到ne[j](跟前面一段的最大匹配)
while (j && s[i] != p[j + 1]) j = ne[j];
if (s[i] == p[j + 1]) j++; // 匹配的话就继续下一位咯
if (j == n) // 匹配成功
{
// 输出匹配的起始位置
printf("%d ", i - n); // 题目的下标是从0开始的,所以就不用i - n + 1了
j = ne[j]; // 匹配成功,还要往后退一步
}
}
return 0;
}
时间复杂度
O(2m)