1.前缀表(两个作用)
首先是理解前缀表,前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。记录下标i之前(包括i)的字符串中的最大相同前后缀长度。
2.前后缀概念
前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串;后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。如 “aabaaf” 中,"aabaa"是最长前缀,"abaaf"是最长后缀。“a”的前后缀长度都为0。
3.前缀表计算方法
如字符串"aabaaf",
长度为前1个字符的子串a,最长相同前后缀的长度为0。
长度为前2个字符的子串aa,最长相同前后缀的长度为1。
长度为前3个字符的子串aab,最长相同前后缀的长度为0。
长度为前4个字符的子串aaba,最长相同前后缀的长度为1。
长度为前5个字符的子串aabaa,最长相同前后缀的长度为2。
长度为前6个字符的子串aabaaf,最长相同前后缀的长度为0。
那么把求得的最长相同前后缀的长度就是对应前缀表的元素,如图:
4.前缀表与next数组
next数组就可以是前缀表,但是很多实现都是把前缀表统一减一(或者右移一位,初始位置为-1)之后作为next数组。
如上面的前缀表0 1 0 1 2 0,
统一减一为:-1 0 -1 0 1 -1,
右移一位为: -1 0 1 0 1 2。
5.代码构造next数组
构造next数组其实就是计算模式串s前缀表的过程,next[i] 表示 i(包括i)之前最长相等的前后缀长度(其实就是j)。 主要有如下四步:
1.初始化
2. 处理前后缀不相同的情况
3. 处理前后缀相同的情况
4. 更新next数组值
class Solution {
public:
void getNext(int* next, const string& s) {
//定义两个指针i和j,
//j指向前缀终止位置(同时也是前缀长度),
//i指向后缀终止位置。因为j初始化为0,那么i就从1开始,进行s[i] 与 s[j]的比较。
int j = 0;
next[0] = 0; //初始化,一个字符时前后缀长度都为0
for(int i = 1; i < s.size(); i++) {
while (j > 0 && s[i] != s[j]) { //前后缀不同
j = next[j - 1]; //向前回溯
}
if (s[i] == s[j]) { //找到相同前后缀
j++;
}
next[i] = j;
}
}
//在文本串s里 找是否出现过模式串t。
//定义两个下标j 指向模式串起始位置,i指向文本串起始位置.
int strStr(string s, string t) {
if (t.size() == 0) {
return 0;
}
int next[t.size()];
getNext(next, t);
int j = 0; //初始化
for (int i = 0; i < s.size(); i++) {
while(j > 0 && s[i] != t[j]) { //不匹配
j = next[j - 1];
}
if (s[i] == t[j]) { //匹配
j++;
}
if (j == t.size() ) { //文本串s里出现了模式串t
return (i - t.size() + 1);
}
}
return -1;
}
};