KMP算法
本文通过解读Leetcode.28 问题来大概解释KMP算法
例如一个主字符串 “aabaabaaf” 需要匹配一个子字符串 “aabaaf”
-
前缀与后缀
以上文子字符串(aabaaf)为例,前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串,例如a,aa,aab,aaba,aabaa都是属于前缀,后缀也由此可知,不包括第一个字符并以最后一个字符结尾的连续子串,如 f,af,aaf,baaf,abaaf都是后缀,那么此时就有一个关键点,就是求前缀表。 -
前缀表
求前缀表的关键就在于找到最长相等前后缀,如a最长相等前后缀长度为0,aa为1,aaa为2等等。
前缀表的作用就是告诉我们上次已经匹配好的位置,如果这次匹配不成功,就通过查找此表找到上一次字符匹配的位置,那么如何计算前缀表。 -
计算前缀表
例如字符串aabaaf,从头开始遍历,第一个子串 a 的最长相等前后缀为0,aa 为 1,aab 为 0,aaba 为 1,aabaa 为 2,aabaaf 为 0。所以求得的最长相等前后缀其实就是前缀表的数值。如果看不懂文字可以看以下代码(Java),前缀表一般用next数组表示,s为子字符串(如上的aabaaf),代码块省略了next数组初始化。
/**
next 数组初始化, int[] next = new int[s.length()];
**/
public void getNext(int[] next,String s){
int j = 0;
for(int i = 1;i<s.length();i++){
while(j>0 && s.charAt(i) != s.charAt(j)){
j = next[j-1];
}
if(s.charAt(i) == s.charAt(j)){
j++;
}
next[i] = j;
}
}
j = next[j-1] 这一步就是查前一个匹配的位置
由上代码块可以自己尝试模拟求取 aabaaf 前缀表的过程。此时aabaaf对应的数组就是[0,1,0,1,2,0]。
求完前缀表基本上就可以解出这道题了。以下放出本题总代码。最后的主字符串和子字符串的匹配又类似于求前缀表。
class Solution {
//前缀表(不减一)Java实现
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;
}
}
}