对每个后缀i,前缀 j 都会完整地跑一次,若匹配则同时++,不匹配则 j 回退
最长相等前后缀
文章中字符串的前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串。
后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。
因为前缀表要求的就是相同前后缀的长度。
这样一来,当前位置 i 不匹配,前 i - 1位置(即后缀部分)是先前匹配好的,此时相当于在找前 i -1 部分的头尾相等的部分。下一步就可以把标准串的开头衔接到此处。
如何计算前缀表
下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。
再来看一下如何利用 前缀表找到 当字符不匹配的时候应该指针应该移动的位置。如动画所示:
找到的不匹配的位置, 那么此时我们要看它的前一个字符的前缀表的数值是多少。
为什么要前一个字符的前缀表的数值呢,因为要找前面字符串的最长相同的前缀和后缀(当前位置已经不匹配了)。
前一个字符的前缀表的数值是2, 所有把下标移动到下标2的位置继续比配。
最后就在文本串中找到了和模式串匹配的子串了。
构造next数组
我们定义一个函数getNext来构建next数组,函数参数为指向next数组的指针,和一个字符串。
构造next数组其实就是计算模式串s,前缀表的过程。 主要有如下三步:
- 初始化
- 处理前后缀不相同的情况
- 处理前后缀相同的情况
推荐版本!数组元素不会-1
class Solution {
public:
void getNext(int* next, const string& s) {
**int j = 0;
next[0] = 0;**
for(int i = 1; i < s.size(); i++) {//后缀的第一位从下标1开始,因为至少2个字符才有后缀嘛;对每一个i(后缀),j都会前前后后跑一遍
while (j > 0 && s[i] != s[j]) {//前缀的第一位从下标0开始;若无奈回退到j = 0,则说明没有相等的前后缀,跳出循环
j = next[j - 1];前后缀不等,j会按数组持续回退,逐位查验少一位时是否会有匹配的希望
}
if (s[i] == s[j]) {//相等时,j往后移动,尝试扩大匹配范围,此时已经跳出while循环
j++;//i 的增加在for循环里
}
next[i] = j;//将j(前缀的长度)赋给next[i], next[i]要记录每个位置相同前后缀的长度
}
}
int strStr(string haystack, string needle) {
if (needle.size() == 0) {
return 0;
}
int next[needle.size()];
getNext(next, needle);
int j = 0;
for (int i = 0; i < haystack.size(); i++) {
while(j > 0 && haystack[i] != needle[j]) {
j = next[j - 1];// 不匹配时,j 来到前一位的“记忆”位置
}
if (haystack[i] == needle[j]) {
j++;// 匹配时 j 后移
}
if (j == needle.size() ) {//若 j 成功走到队尾 ,成功!
return (i - needle.size() + 1);
}
}
return -1;
}
};