注: KMP详细解析 https://programmercarl.com/0028.%E5%AE%9E%E7%8E%B0strStr.html#其他语言版本
题目: 给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。
说明: 当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与 C 语言的 strstr() 以及 Java 的 indexOf() 定义相符。
示例 1: 输入:haystack = “hello”, needle = “ll” 输出:2 示例 2: 输入:haystack = “aaaaa”, needle = “bba” 输出:-1 示例 3: 输入:haystack = “”, needle = “” 输出:0
提示: 0 <= haystack.length, needle.length <= 5 * 104 haystack 和 needle 仅由小写英文字符组成
题解: KMP算法
- 前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串。
- 后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。
- 前缀表要求的就是相同前后缀的长度。
长度为前1个字符的子串a,最长相同前后缀的长度为0。 长度为前2个字符的子串aa,最长相同前后缀的长度为1。 长度为前3个字符的子串aab,最长相同前后缀的长度为0。 长度为前4个字符的子串aaba,最长相同前后缀的长度为1。 长度为前5个字符的子串aabaa,最长相同前后缀的长度为2。 长度为前6个字符的子串aabaaf,最长相同前后缀的长度为0。
那么把求得的最长相同前后缀的长度就是对应前缀表的元素,如图:
- 找到的不匹配的位置, 那么此时我们要看它的前一个字符的前缀表的数值是多少。
- 为什么要前一个字符的前缀表的数值呢,因为要找前面字符串的最长相同的前缀和后缀。所以要看前一位的前缀表的数值。
- 前一个字符的前缀表的数值是2, 所有把下标移动到下标2的位置继续比配。
- 最后就在文本串中找到了和模式串匹配的子串了。
KMP算法中的next数组就可以是前缀表,但是很多实现都是把前缀表统一减一(右移一位,初始位置为-1)之后作为next数组。其实这并不涉及到KMP的原理,而是具体实现,next数组即可以就是前缀表,也可以为前缀表统一减一之后的值(右移一位,初始位置为-1)。
复杂度分析 时间复杂度:O(n+m),其中 n 是字符串 haystack 的长度,m 是字符串 needle 的长度。我们至多需要遍历两字符串一次。
空间复杂度:O(m),其中 m 是字符串 needle 的长度。我们只需要保存字符串 needle 的前缀函数
class Solution {
public:
void getnext(int* next,string &s){
int j=-1;
next[0]=j;
for(int i=1;i<s.size();i++){
while(j>=0&&s[i]!=s[j+1]){
j=next[j];
}
if(s[i]==s[j+1]){
j++;
}
next[i]=j;
}
}
int strStr(string haystack, string needle) {
if(needle.size()==0){
return 0;
}
int next[needle.size()];
getnext(next,needle);
int j=-1;
for(int i=0;i<haystack.size();i++){
while(j>=0&&haystack[i]!=needle[j+1]){
j=next[j];
}
if(haystack[i]==needle[j+1]){
j++;
}
if(j==(needle.size()-1)){
return (i-needle.size()+1);
}
}
return -1;
}
};
|