特点
假定文本串长度为n, 模式串长度为m, 朴素字符串比较时间复杂度为O(n*m),KMP算法时间复杂度为O(n+m)
朴素算法在匹配失败后重新从首部开始匹配,而KMP则利用模式串本身的特点,在匹配失败时不用每次都从模式串首部开始匹配,
关键就在于利用模式串的前缀和后缀相同的部分
比如char[] p = "abdabe"
, 当p[5] = e匹配失败时,朴素算法是重新从p[0]开始匹配,但通过观察,我们发现p[0,1]和p[3,4]相同,可以从p[3]开始匹配
这正是KMP的思想,next数组就对应着前面需要的关键观察信息(知道p[0,1] = p[3,4]相同),在这里next[4] = 1;
计算next数组
令next[i-1] = k 表示[0, i-1]的最长真前缀后缀的索引k(不包括p[0, i]),即p[0, k] = p[i-k-1, i-1]相同
如何求p[0, i]的最长真前缀后缀呢?
- p[i] == p[k+1], 显然next[i] = k+1
- p[i] != p[k+1], next[i] < k+1, 这时仍然有p[0, k] = p[i-k-1, i-1]相同, 我们再次在p[0, k]中找到最长前缀后缀,k = next[k], 然后在其后的位置检验
证明:
- 若next[i] = k+1, 则p[i] == p[k+1],与题设不符
- 若next[i] > k+1, 则next[i-1] > k,与题设不符
还是以上面的例子来说明,根据定义最后一个b位置next[4]=1,
- 如果e位置能够和d位置匹配,则next[5]=1+1=2很合理
- 如果e位置不能喝d位置匹配,则问题演化成d之前ab的前缀最长有多少和e之前的ab的后缀重合,这也就是next[2]
例题
class Solution {
private:
vector<int> next;
void ComputeNext(const string& p){
int n = p.size();
next.resize(n);
int k = -1;
next[0] = k;
for(int i = 1; i < n; i++){
while(k > -1 && p[k+1] != p[i]){
k = next[k];
}
if(p[k+1] == p[i]) k++;
next[i] = k;
}
}
int KMP(const string &t, const string &p){
ComputeNext(p);
int k = -1;
for(int i = 0; i < t.size(); i++){
while(k > -1 && p[k+1] != t[i]){
k = next[k];
}
if(p[k+1] == t[i]) k++;
if(k == p.size() - 1) return i - p.size() + 1;
}
return -1;
}
public:
int strStr(string haystack, string needle) {
return needle == "" ? 0 : KMP(haystack, needle);
}
};