本题使用的方法为 KMP 算法,KMP算法是一种字符串匹配算法,全称是 Knuth-Morris-Pratt 算法,主要思想是利用已知信息来避免无效的字符比较,从而提高匹配效率。
本题关键点在于构建 next 跳转表,难点在于在getNext 和 strStr 函数中理清 while 不匹配时的逻辑,不匹配的代码需要放在 if 匹配的代码前面,这样在不匹配回退到下标0时还可以对文本串当前的字符和模式串最开始的字符进行匹配,不然会导致如果文本串当前是匹配的,却因为下标++匹配掉了。
时间复杂度:O(m+n),空间复杂度:O(n),其中m为文本串haystack的长度,n为模式串needle的长度。
getNext 函数思路:
- 初始化 j 为0,next[0] 为 0,表示第一个字符不存在前缀和后缀,从第二个字符开始遍历。
- 在遍历过程中,比较 s[i] 和 s[j] 是否相等,如果不相等,则j向前回退,更新 j 值为 next[j-1],并重复该步骤,直到 j 为 0 或者 s[i] 和 s[j] 相等。
- 当 s[i] 和 s[j] 相等时,将 j 加1,表示匹配成功的子字符串长度,同时将 j 值赋给 next[i],记录在该位置上前缀和后缀匹配的长度,同时也是在遇到冲突时,在这种前缀情况下应该返回的位置。最后遍历完毕后,得到 next 数组。
strStr 函数思路:
- 从前往后遍历文本串 haystack,以 fast 指针记录当前匹配的位置。
- 在遍历文本串时,如果当前字符与模式串的对应位置不同,则需要将模式串的指针根据 next 数组中发生冲突位置的上一个位置的值进行回退,回到上一个匹配的位置。
- 如果文本串当前字符与模式串的对应位置相同,则将模式串和文本串指针向后移动一位。
- 如果到达模式串的末尾,则说明文本串 haystack 中出现了完整的模式串 needle,返回用当前位置计算的匹配的起始位置。
代码:
class Solution {
public:
void getNext(int *next, string &s) {
int j = 0; // j是匹配的前缀的末尾,同时也是匹配的子字符串的长度
next[j] = 0;
for (int i = 1; i < s.length(); i++) { // i是匹配的后缀的末尾
while (s[i] != s[j] && j > 0) { // 前后缀不相同了,while是因为可能出现多次回退的情况
j = next[j - 1]; // 向前回退
// next[i] = j; // 同下
}
if (s[i] == s[j]) { // 找到了相同的前后缀
j++;
// next[i] = j; // 将j(匹配的子字符串的长度)赋给next[i],即在遇到冲突时,在这种前缀情况下应该返回哪个地方继续匹配
}
next[i] = j; // 写在外面是因为当j=0时,s[i]和s[j]不匹配也要给next[i]赋值
}
}
int strStr(string haystack, string needle) {
if (needle.length() == 0)
return 0;
int next[needle.length()];
getNext(next, needle);
int i = 0; // needleIndex
for (int fast = 0; fast < haystack.length(); fast++) {
while (haystack[fast] != needle[i] && i > 0) { // 不匹配
i = next[i - 1]; // 寻找之前匹配的位置
}
if (haystack[fast] == needle[i]) { // 匹配,fast和i同时向后移动,fast的移动在for循环里
i++;
if (i == needle.length()) // 文本串haystack里出现了完整的模式串needle
return (fast - needle.size() + 1);
}
}
return -1;
}
};