主要是回顾一下kmp算法。
kmp分两个步骤,1,找next数组;2,匹配。
kmp之所以困惑的重要原因就在于,不知道找next数组的时候实在干什么。
这里用一句话说明白:找next[i],其实是在找,使得pattern[0:k)==pattern[i - k: i)的最大的k(注意是左闭右开区间)。
done!
为什么要找这个使得 i 前的 k 个字符和pattern从0开始的 k 个字符相同的最大的k呢?这个从蠢办法谈起,比较容易理解。朴素的算法是,如果不匹配,那么pattern向后挪一位,继续从pattern[0]开始匹配。
考虑匹配到第 pattern[m],此时被匹配的串为s,匹配到了第 n 个位置,但是pattern[m] 和 s[n] 不匹配,也就是说
s[n - m: n) == pattern[0: m),s[n] != pattern[m],那么我们将pattern向后挪一位,开始匹配,如果这时候pattern可以匹配字符串,可以推出,pattern[0: m-1)==s[n - (m - 1): n)==pattern[1:m)。后边两个相等的原因请看红色的式子。
连等的第一项和第三项看到没,这个就是我们之前所说的pattern[0:k)==pattern[i - k: i),此时k=m-1。
也就是说,如果pattern向后挪一位如果匹配,那么必满足pattern[0: i - 1)==pattern[i - (i - 1): i)。
同理,如果pattern向后挪两位如果匹配,那么必满足pattern[0: i - 2) == pattern[i - (i - 2): i)。
以此类推。
所以我们要找的就是:
对pattern的第i个位置,使得pattern[0:k)==pattern[i - k: i)的最大的k(注意是左闭右开区间),这个位置意思是,如果匹配到pattern[i] 不匹配,那么如果最终匹配,那么至少,要重置到pattern[k]开始匹配。
具体找的方式,是用贪心法去找。用贪心法的原因也很简单,当pattern[0:k)==pattern[i-k: i)时,如果pattern[k] == pattern[i],则pattern[0:k + 1)==pattern[(i+1)-(k+1): i+1),也就是说,对于pattern的第i个位置,如果 i 前的 k 个字符和pattern从0开始的 k 个字符相同,对 pattern 的第 i+1 个位置,只需要检查pattern第 i+1 个位置和第 k 个位置是否相同就可以,如果相同,那是大好事(第 i+1 个位置的最大的k也找到了),如果不同,才需要特殊处理,处理的方式类似回溯🤔。
class Solution {
public:
int *next;
int strStr(string haystack, string needle) {
return kmp(haystack, needle);
}
int kmp(const string &s, const string &pattern) {
this->next = new int[pattern.length() + 5];
memset(this->next, 0, pattern.length() + 5);
this->next[0] = -1;
this->next[1] = 0;
int index = 0;
for (int i = 2; i < pattern.length(); ) {
if (index == -1) {
this -> next[i] = (pattern[i - 1] == pattern[0]);
index = 0;
i ++;
}
else if (pattern[i - 1] == pattern[index]) {
this -> next[i] = ++ index;
i ++;
}
else {
index = this->next[index];
}
}
int i, j;
for (i = 0, j = 0; i < s.length() && j < pattern.length(); ){
if (s[i] == pattern[j]){
i ++;
j ++;
}
else {
j = next[j];
if (j == -1) {
j = 0;
i ++;
}
}
}
if (j == pattern.length()) {
return i - pattern.length();
}
return -1;
}
};