不得不承认,@皎月半洒花大佬讲的比我规范,但看不懂的话可以看看我的。
用途
在字符串初级算法里,而其中最经典的模型问题就是判断一个串是否是另一个串的子串。我们常用 KMP 算法解决这类问题。
题目描述
给定两个字符串 s1s_1s1 和 s2s_2s2,求出 s2s_2s2 在 s1s_1s1 中所有出现的位置和 s2s_2s2 的每个前缀 s′s's′ 的最长 border 的长度。。
在本题解中,我用 nnn 代替 ∣s1∣|s_1|∣s1∣(即 s1s_1s1 的长度),用 mmm 代替 ∣s2∣|s_2|∣s2∣。
暴力匹配
枚举 s1s_1s1 的每一个起始位置 i(0≤i<n−m)i(0\le i<n-m)i(0≤i<n−m),看从 iii 位置开始往后长度为 mmm 的子串是否是可以和 s2s_2s2 匹配
判断两个串匹配成功的条件是:两个串长度一致,两个串每一个对应位置字符都完全一样。
本做法时间复杂度为 O(nm)O(nm)O(nm),只能得 70 分,比总司令高。
更优做法:KMP
定义
定义一个字符串性质——前缀后缀最大值,即题目中要求的 border(让你求那多半要用)。
定义一个字符串 sss 的 border 为 sss 的一个非 sss 本身的子串 ttt,满足 ttt 既是 sss 的前缀,又是 sss 的后缀。
我将字符串 sss 的前缀 s′s's′ 的最长 border 的长度称为 next∣s′∣next_{|s'|}next∣s′∣,别问我为啥。
换句话说,nextinext_inexti 就是遍历到字符串 s2s_2s2 的第 iii 位时,与已经匹配成功的部分的后缀相同的最大的长度。我代码中的 kmp[i] 就是 nextinext_inexti。
匹配过程
举例说明,其中 s1s_1s1 为 ABACABACABD,s2s_2s2 为 ABACABD。
先将 s1s_1s1 和 s2s_2s2 的第一位对齐并看对应位是否相同,我们发现一路畅通,但最后一个匹配失败了。若这时对齐 s1s_1s1 的第二位和 s2s_2s2 的第一位将 s2s_2s2 重新匹配一遍就成了暴力。
i
ABACABACABD
ABACABD
j
仔细回想 nextnextnext 定义发现:失配后,我们可以直接跳到 nextjnext_jnextj,即与已匹配部分相同的 s2s_2s2 最大前缀的最后一位的下一位,手动得出此时 nextj=2next_j=2nextj=2。然后就会变成这样:
i
ABACABACABD
ABACABD
j
这样做,复杂度为 O(n+m)O(n+m)O(n+m)。
求 nextnextnext
将 s2s_2s2 自己和自己匹配,过程和上面相仿。匹配到 jjj 失败时,记录 nextinext_inexti 为 jjj。匹配时一个字符串不动,另一个向后移,iii 为匹配到不动的 s2s_2s2 的位置,jjj 为匹配到要动的 s2s_2s2 的位置。
代码实现
#include<bits/stdc++.h>
using namespace std;
const int N=1e6+2;
int kmp[N],la,lb,j;
//kmp即Next数组,表示相同前缀后缀最大值(不是对称的最大值)
string a,b,A,B;
int main(){
cin>>A>>B;
a='\0'+A,b='\0'+B;
la=a.size()-1,lb=b.size()-1;
for(int i=2;i<=lb;i++){//求kmp
while(j&&b[i]!=b[j+1]) j=kmp[j];
if(b[j+1]==b[i]) j++;
kmp[i]=j;
}
j=0;
for(int i=1;i<=la;i++){//匹配
while(j>0&&b[j+1]!=a[i]) j=kmp[j];
if(b[j+1]==a[i]) j++;
if(j==lb){printf("%d\n",i-lb+1);j=kmp[j];}
}
for(int i=1;i<=lb;i++) printf("%d ",kmp[i]);
return 0;
}

被折叠的 条评论
为什么被折叠?



