目录
一、引入
KMP算法作为经典的字符串匹配算法,确实很难理解,我尽量解释清楚我的理解。
因为该算法大体上可分为两块(kmp的原理,next数组的推导),该博客将详细分析next的数组的推导。(ps:大约有两种next数组的写法,我使用的是next[x]数组的值是包括x的最大前后缀
例:模式串 abcab next数组:00012)
二、问题解决
相信来看的兄弟都是对kmp的原理有理解的,我就先直接放代码,再根据代码讲解:
//needlee是模式串,next是我们在该函数内要完成的next数组
getNext(string needle, vector<int> &next) {
int n = needle.size();
next[0] = 0;//不必要,为了清晰写出
for(int j = 0, i = 1;i < n;i++) {
while(j != 0 && needle[j] != needle[i]) {
j = next[j - 1];//难点
}
if(needle[j] == needle[i]) {
j++;
}
next[i] = j;
}
}
推导思路
对于next数组的推导,我将其分为几个步骤:
1. j 初始化为0,i 初始化为 1
2. 若needle[i] == needle[j],则此时的next数组值为 j + 1,然后 i j 一起向后移动一格
3. 若needle[i] != needle[j],则 j 变为上一个元素对应next数组中的值除非 j == 0(该过程是循环的)
重点
1. j 的逻辑含义: 在 j 对应的字符前已经有 j 个元素 与 i 对应的字符的前 j 个元素相等
例:对于模式串:aabaabaaa的next数组推导过程中,中有一个 i j 状态如下图所示:
此时,j == 5, 即在 j 对应的 b 之前已经有 aabaa 和 i 对应的 a 之前的 aabaa 相同了
j 有一种储存器的感觉,如果读者有心自己推一下,会发现 j 在该字符串中是1 2 3 4 5递增的
即:a == a => aa == aa => aab == aab => aaba == aaba => aabaa == aabaa
2. 为什么needle[i] != needle[j]时就要 j 变为上一个元素对应next数组中的值?
注意 j 的逻辑意义,此时的不相等说明了 j "储存"的 5 个元素数量太多,不能满足条件,所以需要进行缩减,那么缩减到哪里呢? 自然需要相等的尽可能长的前缀与尽可能长的后缀。
那么问题就转化为了 "尽可能长的相等前后缀在哪里?" 以下是我的理解以及证明
还看上方这个例子,在该情况下 i 与 j 对应的值不等,根据上方规则 j 要移动到前面的 a 对应的next 值即 数组下标为 2 位置,因为此时 j == 5 即在 j 对应的字符前已经有 5 个元素与 i 对应的元素的前 5 个元素相等,也即 aabaa① == aabaa②
注意此时的 对应next为 2 的 a 它的意义是 对于 aabaa① 有最长公共前后缀是 aa 和 aa
所以后缀的 aa 可以对应到 ② 的后缀 aa 所以此时就有最长的 ① 的前缀aa 与 ② 的后缀 aa 相等。
注意点和一些小结论
1. 不要把next数组的值和模式串的下标值弄混
2. j 一般而言不靠近 i 且在模式串的前侧徘徊(这可以帮你理解 j 的逻辑意义)
3. j 的值就是next数组的下标值。
4. 在 j == 0 时 若 j i 对应的值不匹配 那么结果是 i 向后一格。
5. 自己多推,多debug 总能理解的。
完整代码
class Solution {
public:
void getNext(string needle, vector<int> &next) {
int n = needle.size();
next[0] = 0;
for(int j = 0, i = 1;i < n;i++) {
while(j != 0 && needle[j] != needle[i]) {
j = next[j - 1];
}
if(needle[j] == needle[i]) {
j++;
}
next[i] = j;
}
}
int strStr(string haystack, string needle) {
int m = haystack.size(), n = needle.size();//m为主串长度 n为模式串长度
if(n == 0) return 0;
vector<int> next(n);
getNext(needle, next);//初始化next数组
for(int i = 0, j = 0;i < m;i++) {
while (j != 0 && haystack[i] != needle[j]) {
j = next[j - 1];
}
if (haystack[i] == needle[j]) {
j++;
}
if(j == n) {//结束条件
return i - n + 1;
}
}
return -1;
}
};