KMP
KMP#
问题模式##
给定模式串s1,目标串s2,问s1出现在s2的次数以及位置
期望复杂度:O(n+m)
算法部分##
常规而言,如果之间去扫描目标串并check是否匹配,复杂度就会达到O(n*m)
很自然为了降低复杂度,我们需要更好的方式
比如当aaa匹配成功,我们先不看后面的情况,单论aaa本身:
模式串:aaa
欲匹配的目标串: aaa张三李四王五...
目标串1: aaa张三李四王五...
目标串2: aa张三李四王五...
目标串3: a张三李四王五...
如果aaa能匹配,目标串1中前3个a必能匹配,目标串1中前2个a必能匹配,目标串1中前1个a必能匹配,而目标串2,3是由目标串1后移的结果
所以,如果模式串某个位置能够匹配成功,可能会存在前j个元素在后移过程中也能够完全匹配
我们现在需要记录next[i]=j,他的含义是:
如果模式串中位置i匹配成功,一定存在模式串前j个元素能够完全匹配
(这里位置i是字符串下标,从0开始计数,j是个数,可能为0,注意区分)
也称为位置i之前的字符串中最长相同前后缀的长度为j
例如对于aaabaaaai,有:
模式串:aaabaaaai
对应的:012012330
失配处理:我们如果有了next数组,那我们在i失配的时候就可以一定可以说前j个元素一定是匹配的,现在只需要check模式串第j号下标与当前目标串位置元素的关系,不匹配就继续进行当前失配处理。
KMP的精髓之处在于如何找这个next数组:
其实这个过程运用的就是上面一样的过程,一言以蔽之,“自己匹配自己”
现在只不过将原来的模式串当作现在的目标串来匹配,使用的是一样的过程,
对于位置i而言,next[i]就是他当前能匹配成功的个数
还有一点,匹配成功了怎么办?
判断成功,触发反馈,再当作失配来处理,
这两块匹配过程可以拆开写,也可以合起来写,下面展示的核心代码是合起来的。
核心代码##
s1是目标串,s2是模式串
一定要区分好下标与个数的差别,很容易出错!!
s1=s2+"#"+s1;//连接处理,让两个过程合并,记得加上#,否则一些特例会直接暴毙
next[0]=0;
for(int i=1;i<s1.size();i++) //这里指的是字符串下标的遍历,从标号1开始
{
//模式串中第0号到第c-1号均已经匹配,匹配个数是c个,开始检测第c号下标
while(c!=0&&s1[c]!=s1[i])
c=next[c-1];
if(s1[c]==s1[i])c++;//if(1)那么说明第c号下标匹配成功,那么有c+1个数已经匹配成功
if(c==len&&i>len) //匹配成功了继续当作失配处理
{
cout<<i-len*2+1<<endl;
c=next[c-1];
}
next[i]=c; //c指的是当前匹配成功的个数
}
洛谷P3375模版##
#include<iostream>
#include<cstring>
#include<iostream>
#include<string>
using namespace std;
#define INF 1e10+5
#define maxn 10005
#define minn -105
#define ld long double;
#define uint unsigned int;
#define ull unsigned long long;
typedef long long ll;
int main()
{
string s1,s2;
int c;
int next[maxn<<1];
cin>>s1;
cin>>s2;
int len=s2.size();
s1=s2+"#"+s1;
next[0]=0;
for(int i=1;i<s1.size();i++)
{
while(c!=0&&s1[c]!=s1[i])
c=next[c-1];
if(s1[c]==s1[i])c++;
if(c==len&&i>len)
{
cout<<i-len*2+1<<endl;
c=next[c-1];
}
next[i]=c;
}
for(int i=0;i<len;i++)
cout<<next[i]<<" ";
return 0;
}
CF Global Round 7 D2##
核心问题是求前缀最长回文串
注意所求内容是具有前缀性的,所以它模式串是确定的,只不过模式串的长度不定
而将回文串逆置,则根据回文串的性质,这个就是目标串