28.找出字符串中第一个匹配项的下标
要点
- 什么是前缀表? 记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。
- 前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串;后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串
i指向后缀的末尾,j指向前缀中相同前后缀子串的下一个元素(即,当比较发现不同时,跳向的下一个比较位置)j要增加只能+1
前缀表即next数组
class Solution {
public:
void getnext(int* next, const string& s){
int j = 0;
next[0] = 0;
for (int i = 1; i < s.size(); i++) {
// 不匹配!跳回
while (j>0 && s[i] != s[j]) {
j = next[j-1];
}
// 匹配就+1
if (s[i] == s[j]) j++;
next[i] = j;
}
}
int strStr(string haystack, string needle) {
int next[needle.size()];
getnext(next, needle);
int j = 0;
// i 只能向前走,有且只有两种情况
// 1. j != 0, ij指向的字符匹配时,一起后移一位
// 2. j == 0, 第一位就不匹配,i后移
// 其他情况都是j回退,找字符去匹配i的指向
for (int i = 0; i < haystack.size(); i++) {
while (j>0 && haystack[i] != needle[j]) {
j = next[j-1];
}
// 执行到这里的情况:
// 1. j == 0 -> haystack[i] == needle[0] 2. haystack[i] == needle[j]
// \->haystack[i] != needle[0]
if (haystack[i] == needle[j]) j++; // j先+1,i在循环体末尾+1
// j将needle都匹配完了,匹配成功,返回
if (j == needle.size()) return i-needle.size()+1;
}
return -1;
}
};
难点
- getnext函数和主函数结构类似,主要有如下三步:
- 初始化
- 处理前后缀不相同的情况 (while)
- 处理前后缀相同的情况 (if)
总结
n为文本串长度,m为模式串长度,因为在匹配的过程中,根据前缀表不断调整匹配的位置,可以看出匹配的过程是O(n),之前还要单独生成next数组,时间复杂度是O(m),所以整个KMP算法的时间复杂度是O(n+m)的。
暴力的解法显而易见是O(n × m)