最长前后缀公共子串
一个字符串的最长前后缀公共造子串指这个字符串的前缀和后缀的最长公共子串,但不能是字符串本身。比如 a b c d a b c abcdabc abcdabc的最长前后缀公共子串为 a b c abc abc, a a a a a a aaaaaa aaaaaa的最长前后缀公共子串为 a a a a a aaaaa aaaaa。
KMP
给出长度为 n n n文本串A和长度为 m m m模式串B,要在A中查找B出现的位置。朴素算法的时间复杂度是 O ( n m ) O(nm) O(nm)的,效率较低。于是KMP就派上用场了。
算法流程
假设文本串A匹配到 i i i位置,模式串B匹配到 j j j位置
1.如果 j = − 1 j=-1 j=−1,或字符串匹配成功(即 a [ i ] = = b [ j ] a[i]==b[j] a[i]==b[j]),则 i + + i++ i++, j + + j++ j++,继续匹配下一个字符
2.如果 j ! = − 1 j!=-1 j!=−1,且当前字符串匹配失败(即 a [ i ] ! = b [ j ] a[i]!=b[j] a[i]!=b[j]),则 i i i不变, j = n x t [ j ] j=nxt[j] j=nxt[j]。即失配时,B相对于A向右移动了 j − n x t [ j ] j-nxt[j] j−nxt[j]位
n x t nxt nxt数组的意义在于当模式串失配时, n x t nxt nxt值告诉你在下一步匹配时跳到哪个位置。
如果字符串是 a b a a abaa abaa
模式串 | a | b | a | b |
---|---|---|---|---|
最大前缀后缀公共子串长度 | 0 | 0 | 1 | 2 |
模式串 | a | b | a | b |
---|---|---|---|---|
next数组 | -1 | 0 | 0 | 1 |
那么如何求 n x t nxt nxt数组呢?
我们可以用递推的方法,假设 n x t nxt nxt数组中0到i-1都以求出,现在求 n x t [ i ] nxt[i] nxt[i]。
如果 s [ i − 1 ] = = s [ n x t [ i − 1 ] ] s[i-1]==s[nxt[i-1]] s[i−1]==s[nxt[i−1]],则 n x t [ i ] = n x t [ i − 1 ] nxt[i]=nxt[i-1] nxt[i]=nxt[i−1]
否则,继续判断 s [ i − 1 ] s[i-1] s[i−1]是否等于 n x t [ n x t [ i − 1 ] ] nxt[nxt[i-1]] nxt[nxt[i−1]]
如果相等,则 s [ i ] = n x t [ n x t [ i − 1 ] ] + 1 s[i]=nxt[nxt[i-1]]+1 s[i]=nxt[nxt[i−1]]+1
否则继续判断,直到相等或 n x t nxt nxt值为 − 1 -1 −1为止。
时间复杂度为 O ( n + m ) O(n+m) O(n+m)。
code
char s[N];
void gt(){
nxt[0]=-1;
int len=strlen(s);
for(int i=1;i<len;i++){
int j=nxt[i-1];
while(j!=-1&&s[j]!=s[i-1]) j=nxt[j];
nxt[i]=j+1;
}
}
接下来就是文本串和模式串的匹配。
code
char s1[N],s2[N];
int kmp(){
inr l1=strlen(s1),l2=strlen(s2);
if(l1<l2) return -1;
gt(s2);
for(int i=0,j=0;i<=l1;i++,j++){
if(j==l2) return i-j;
if(i==l1) return -1;
while(j!=-1&&s1[i]!=s2[j]) j=nxt[j];
}
}
例题
code
#include<iostream>
#include<cstring>
#include<cstdio>
using namespace std;
int ans,nxt[1000005],ex[1000005];
char s1[1000005],s2[1000005];
void getnxt(){
nxt[0]=-1;
int l=strlen(s2);
for(int i=1;i<l;i++){
int j=nxt[i-1];
while(j!=-1&&s2[j]!=s2[i-1]) j=nxt[j];
nxt[i]=j+1;
}
}
int KMP(){
int l1=strlen(s1),l2=strlen(s2);
if(l1<l2) return -1;
getnxt();
for(int i=0,j=0;i<=l1&&j<=l2;i++,j++){
if(i==l1) return -1;
if(j==l2-1&&s1[i]==s2[j]) printf("%d\n",i-j+1),j=nxt[j];
while(j!=-1&&s1[i]!=s2[j]) j=nxt[j];
}
return -1;
}
int main()
{
scanf("%s%s",s1,s2);
KMP();
int l=strlen(s2);
for(int i=0;i<l;i++){
int ans=nxt[i];
while(ans!=-1&&s2[ans]!=s2[i]) ans=nxt[ans];
printf("%d ",ans+1);
}
}