KMP主要应用在字符串匹配上。
其他废话不多说,本文主要在他人对KMP算法讲解的基础上加入我自己的想法,便于我自己理解,同时也希望能帮助大家更好理解KMP算法。如果哪里有问题,还望评论指正。
示例:
文本串:aabaabaafa
模式串:aabaaf
暴力法:
而KMP算法通过前缀表可以做到这样:
比暴力法快很多
那么前缀表怎么构建呢
前缀表
一.前缀和后缀
前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串。
后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。
例如:aabaaf
这里的最长相同前后缀长度即可构成前缀表,而next数组也可以就是前缀表
但这里是我们手动计算的,如何用代码来实现呢?
二.next数组——实现思路
具体可以分为以下几步:
1.初始化
2.前后缀相同的情况
3.前后缀不相同的情况
- 先来解决第一步:初始化
定义两个指针 i 和 j,j指向前缀末尾位置,i指向后缀末尾位置。然后还要对next数组进行初始化赋值,如下:
int j = 0;
next[0] = 0;
j 从位置0开始,i 从位置1开始,并需要一直走到模式串结束,不断比较 s[i] 与 s[j] ,因此外循环为:
for(int i=1;i<s.length();i++){
}
- 接下来解决第二步:前后缀相同的情况
如果 s[ i ] 与 s[ j + 1] 相同,说明找到了最长相同前后缀,因此需要同时向后移动 i 和 j ,i 在下一次循环中会+1,而 j 在此处就++的原因是,因为next 数组中记录的是相同前后缀的长度(一定要记住),而 j 本身表示的是前缀结尾的下标,因此长度是 j+1,即 next[ i ] = j+1;而先 j++的话,next[ i ] = j 就可以了。
if (s[i] == s[j]) { // 找到相同的前后缀
j++;
}
next[i] = j;
- 最后解决第三步:前后缀不相同的情况
若是暴力法,s[i] != s[j] 不相同,j 直接退为0了,但KMP算法需要利用前缀表不断尝试,尽量退到还有前缀可用的位置,万不得已才退到0。
while (j > 0 && s[i] != s[j]) { // 前后缀不相同了
j = next[j-1]; // 向前回退
}
这里面的 j = next[ j-1] 我用了很长时间理解
假设这样一种情况:
当 j 走到 c,i 走到 b 的时候,出现了不相等的情况,这时红圈中的两坨内容是相同的,我们需要在这串前缀中找到尽可能长的子前缀能复用,因此 j 从前缀的尾部的next[]开始判断(即 next[ j-1])。
当找到 s[i] == s[j] 时,可以保证前缀从0到 j-1和后缀从 i 往前数 j 个(不包括 i)一定是相同的,并且可以直接复用第二步的过程。而 s[i] != s[j] 时,还可以继续往前跳,不管怎么跳,从0到 j-1 的前缀内容都等于从i往前数j个的后缀内容。
因为:
笼统来看,下图黑色这两坨内容是相同的,假设第一跳后 s[i] 仍不等于 s[j],j 跳到了 e 的位置,这时说明从0到 j-1(即0到f)与从 c 向前数 j 个(不包括 c)是相同的,因此,图中红色四坨是相同的;再跳一次,这回 j 跳到了 b ,此时 s[ i ] 等于 s[ j ] 了,根据前面的逻辑,图中的八坨黑色圈是相同的。因此next[ i ]= j+1,同样可以复用相同前后缀的 if 代码,先让 j++,再赋值。
三.构造next数组完整代码
- 以力扣题目为例:leetcode-28
public void getNext(int[] next,String needle){
int j = 0;
next[0] = 0;
for(int i=1;i<needle.length();i++){
while(j>0 && needle.charAt(j) != needle.charAt(i)){
j = next[j-1];
}
if(needle.charAt(i)==needle.charAt(j)){
j++;
}
next[i] = j;
}
}
完整题解
文本串与模式串比较也同样,如果相同,则都++,不同就需要向前找 j 的位置。
class Solution {
public int strStr(String haystack, String needle) {
if(needle.length()==0) return 0;
int[] next = new int[needle.length()];
getNext(next,needle);
int j=0;
for(int i=0;i<haystack.length();i++){
while(j>0 && needle.charAt(j) != haystack.charAt(i)){
j = next[j-1];
}
if(haystack.charAt(i) == needle.charAt(j)){
j++;
}
if(j==needle.length()){
return i-j+1;
}
}
return -1;
}
public void getNext(int[] next,String needle){
int j = 0;
next[0] = 0;
for(int i=1;i<needle.length();i++){
while(j>0 && needle.charAt(j) != needle.charAt(i)){
j = next[j-1];
}
if(needle.charAt(i)==needle.charAt(j)){
j++;
}
next[i] = j;
}
}
}