kMP主要应用在字符串匹配上。
KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。
首先需要知道什么是前后缀?
前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串
后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串
模式串与前缀表对应位置的数字表示的就是:下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。
若当前字符不匹配,就看前一位的前缀表的数值,若前一个字符的前缀表的数值是2, 所有把下标移动到下标2的位置继续匹配。
而next数组就可以是前缀表,但是很多实现都是把前缀表统一减一(右移一位,初始位置为-1)之后作为next数组。
原本暴力解法的时间复杂度为$O(n × m)$
利用KMP解法的时间复杂度为$O(n+m)$
使用KMP算法一定要构造next数组
构造next数组
- 初始化
定义两个指针i和j,j指向前缀位置,i指向后缀位置。
next[i]表示i(包括i)之前最长相等的前后缀长度。
2. 处理前后缀不相同的情况
s[i]与s[j+1]进行比较 若不相同则遇到前后缀末尾不相同的情况,需要向前回退。则就是要找j+1前一个元素在next数组里的值。
3. 处理前后缀相同的情况
若s[i]与s[j+1]相同,那么就同时向后移动i和j, 同时将j(前缀的长度)赋给next[i],因为next[i]要记录相同前后缀的长度。
使用next数组来做匹配
注意:本题要在文本串字符串中找出模式串出现的第一个位置 (从0开始),所以返回当前在文本串匹配模式串的位置i 减去 模式串的长度,就是文本串字符串中出现模式串的第一个位置。
完整代码如下:
不减一的情况
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(needle.charAt(j)==haystack.charAt(i)){
j++;
}
if(j==needle.length()){
return i-needle.length()+1;
}
}
return -1;
}
private void getNext(int[] next,String s){
int j=0;
next[0]=0;
for(int i=1;i<s.length();i++){
while(j>0&&s.charAt(j)!=s.charAt(i)){
j=next[j-1];
}
if(s.charAt(j)==s.charAt(i)){
j++;
}
next[i]=j;
}
}
}