从文本串haystack中找到一个与模式串needle相匹配的子串,常规的暴力求解时间消耗很大,目前最常用的就是KMP算法。
KMP算法的核心思想就是利用前缀和后缀相同的部分,也就是最长公共前后缀来帮助匹配。具体的思路这两个回答讲的很好。
https://www.zhihu.com/question/21923021/answer/281346746
https://www.zhihu.com/question/21923021/answer/37475572
先看了这两个回答再来看下面的部分
当在某一处失配时,该处以前的模式串部分和文本串一定是相同的,然后这相同的一部分中,它的前缀和后缀又有相同的部分,这个时候,可以将模式串中相同那个前缀来对应文本串中的那个后缀,这样就能够不会退文本串当前的位置,而只是更新模式串用来与文本串进行匹配的字符。要是还失配,则重复这一步骤,寻找次大的公共前缀后缀串,而这个次大串其实也就是当前公共前缀后缀串的最大公共前缀后缀串。
对于这一部分来说,就是让模式串的前缀作为模式串,模式串自己作为文本串进行匹配,来求next数组。next数组是KMP算法的核心,它记录了模式串的每个位置的最长公共前后缀长度。这个数组用于在匹配失败时快速跳过不必要的比较。
具体代码:
class Solution {
public://求next数组void getNext(int* next, const string& s) {int j = 0; //j表示的是当前这个字符之前已经匹配的前缀长度,也就等于当前字符之前的公共前后缀长;//同时也代表了当前模式串进行匹配的位置next[0] = 0; //next[i]的值表示模式串中从0到i的子串的最长公共前后缀的长度,一个字符的串没有前缀,因此为0for(int i = 1; i < s.size(); i++) { while (j > 0 && s[i] != s[j]) {//不匹配时回退到次大的公共前后缀重新匹配,直到回退到j=0,这时候就相当于重新开始从模式串头匹配j = next[j - 1]; //next[i] = j;}if (s[i] == s[j]) {//新匹配上了一个字符,公共前后缀长+1j++;//next[i]=j;}next[i] = j;}}int strStr(string haystack, string needle) {if (needle.size() == 0) {return 0;}vector<int> next(needle.size());getNext(&next[0], needle);int j = 0;for (int i = 0; i < haystack.size(); i++) {while(j > 0 && haystack[i] != needle[j]) {j = next[j - 1];}if (haystack[i] == needle[j]) {j++;}if (j == needle.size() ) {return (i - needle.size() + 1);}}return -1;}
};
举例:
假设模式串needle = "abababc",我们逐步计算next数组:
i = 0:
next[0] = 0(单个字符没有前缀和后缀)。
i = 1:
s[1] = 'b',j = 0。
s[1] != s[0],j保持为0。
next[1] = 0。
i = 2:
s[2] = 'a',j = 0。
s[2] == s[0],j++变为1。
next[2] = 1。
i = 3:
s[3] = 'b',j = 1。
s[3] == s[1],j++变为2。
next[3] = 2。
i = 4:
s[4] = 'a',j = 2。
s[4] == s[2],j++变为3。
next[4] = 3。
i = 5:
s[5] = 'b',j = 3。
s[5] == s[3],j++变为4。
next[5] = 4。
i = 6:
s[6] = 'c',j = 4。
s[6] != s[4],j = next[3] = 2。
s[6] != s[2],j = next[1] = 0。
s[6] != s[0],j保持为0。
next[6] = 0。
最终,next数组为:[0, 0, 1, 2, 3, 4, 0]。