一、题目

二、思路
2.1 KMP的核心思想
- 与普通的双层暴力遍历相比,KMP算法的精髓在于利用已匹配的前缀信息来优化搜索过程 。当发生不匹配时,算法不是简单地将模式串后移一位重新开始,而是根据预先计算的
next数组,直接将模式串滑动到下一个可能匹配的位置。
2.2 next数组的实质
next[i] 表示在模式串的前 i 个字符组成的子串中,最长的相等真前缀与真后缀的长度。
-
真前缀:不包含最后一个字符的前缀
-
真后缀:不包含第一个字符的后缀
-
核心价值 :当字符匹配失败时,
next[i]告诉我们模式串应该回溯到什么位置继续匹配
2.3 基础递推关系
next[i] 的计算基于 next[i-1] 和当前字符 pattern[i]:
-
已知条件 :若
next[i-1] = lastNextLen,表明子串pattern[0...lastNextLen-1]中,前lastNextLen个字符与后lastNextLen个字符完全相等pattern[0...lastNextLen-1] == pattern[i-lastNextLen...i-1] -
关键比较 :为了高效计算,利用上述
next[i-1]中已经包含的信息,比较pattern[lastNextLen]与pattern[i],就可以得到next[i]
(1) 如果相等 :next[i] = lastNextLen + 1(相等前后缀长度增加1)
(2) 如果不相等 :回溯到 next[lastNextLen] 继续尝试匹配
三、代码
java
class Solution {
public int strStr(String haystack, String needle) {
// 使用KMP算法
// 1、得到 next 数组
int[] next = getNextArray(needle);
// 2、借助 next 数组完成遍历
int i = 0; // 母串指针
int j = 0; // 子串指针
int m = haystack.length();
int n = needle.length();
while(i < m && j < n){
if(j == -1 || haystack.charAt(i) == needle.charAt(j)){
i++;
j++;
}else{
j = next[j];
}
}
return j == n ? i - n : -1; // 判断子串的指针是否已经遍历完成
}
private int[] getNextArray(String s){
// next数组其实是不包含当前字符的使得真前缀与真后缀相同的最大长度
// 也就是 next[i] 要借助 next[i-1] 和 s.charAt(i) 来得到
// 若 next[i-1] = lastNextLen 可知,s 的 [0, lastNextLen-1] 对应的字符串 == s 的 [i-1-lastNextLen, i-1]
// 为最大地利用现有资源,所以先判断 s.charAt(lastNextLen) ?= s.charAt(i)
// 若不等,则继续回溯进行判断
int len = s.length();
int[] next = new int[len];
next[0] = -1;
int lastNext = -1; // 记录上一个next
int i = 0;
while(i < len - 1){
// lastNext == -1:说明已经回溯到开头,没有公共前后缀,next[i+1] = 0
// s[i] == s[lastNext]:当前字符匹配成功,最长公共前后缀长度+1
if(lastNext == -1 || s.charAt(i) == s.charAt(lastNext)){
next[i + 1] = lastNext + 1;
i++;
lastNext++;
}else{ // 字符不匹配时,利用已计算的next信息进行回溯,找到更短的相同前后缀继续尝试匹配
lastNext = next[lastNext];
}
}
return next;
}
}