一、应用题型
首先我们要明白KMP算法要解决的是怎样的问题?一般用来解决字符串匹配的问题,比如有一个文本串s,一个模式串t,要检查s中是否包含有t,或者是s中是否可以由多个t组合在一起而成的等等诸如此类的题目,总的概括就是母串s是否包含一个或者是多个子串t。
二、KMP原理
再将原理之前我们要明白前缀和后缀的概念:前缀就是以首字符开头但不包含尾字符的子串,后缀就是以尾字符结尾但不包括首字符的子串,举个例子:
s = abcab
前缀
a
ab
abc
abca
后缀
b
ab
cab
bcab
下面就引入了最长相同前后缀的概念:有的书上也说明了最长相等前后缀的概念,其实这个是不准确的,因为我们要的是前后缀相同,相同就已经包括了相等,相等只是数量上的一致,并不一对应的字符相同,理解了这个我们接下来就举例说明为啥要引入最长相同前后缀这个概念:
s = aabaabaaf
t = aabaaf
在上面两个字符串中,我们知道t与s进行匹配时,f与b发生冲突,说明了f之前的字符都是匹配的,但是子串到f时已经不匹配了,我们不可能让s中的字符从下表为1的地方开始,子串从头开始匹配,这样的话就浪费了太多的时间,而且大多是无效匹配的,我们希望的就是让母串不要移动,让子串来回移动,但是怎样让子串移动到合适的地方呢?这里就想到了最长公共前后缀的概念,在上面f是冲突的,但是前面是匹配的,我们就找冲突的前一个字符的前后缀长度就好了,这个长度恰好也是前缀后面的那个元素的下标,从这开始匹配,就不用在匹配之前匹配过的了;所以关键就是求解每个字符对应的最长公共前后缀的长度,也就是next数组;
next数组求解方式如下:
void getindex(vector<int>& next, string& s){
int j = 0; //表示前缀的末尾位置,
next[0] = 0;
for(int i = 1;i < s.size();i++){
while(j>0 && s[i]!=s[j]){
j = next[j-1];
}
if(s[i] == s[j]) j++;
next[i] = j;
}
}
next数组就是前缀表,前缀表表示的就是i包括i之前最长的公共前后缀的长度(注意这句话:当前i以及i之前匹配过的最公共前后缀的长度即可),一旦知道了前缀表,我们就是知道了当字串不匹配的时候,该回退到哪里重新开始匹配;
三、应用
求出来前缀表了,下面就是应用了如何应用呢,具体做法如下:
for(int i = 0;i < nums.size();i++){ i表示原字符串的位置,只用一边即可
while(j > 0 && s[i]!=s[j]){ j指向的是匹配字符串的位置
j = next[j-1];
}
if(s[i] == s[j]){
j++;
}
}