原题:
Implement strStr().
Returns the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack.
刚看到这题,我就放了个string.find()上去:
return haystack.find(needle);
完美AC。
但是转念一想,这题虽然是Easy,但是为什么不练练手实现一下KMP算法呢?
算法导论里面,字符串匹配是单独的一章,讲的也比较复杂,当时看的适合还没学过计算理论,有限自动机那一块看的云里雾里,后来重新回头看才理解,但是自动机的实现是比较复杂的。
KMP算法也是看了一下思想核心,本质上就是记录前缀的模式,但是因为章节比较长,以为实现起来很麻烦。
这次重新写一遍才发现,真的很简单……
下面是正文!!——KMP
KMP算法的核心是找到一个next数组,我们知道,在字符串匹配的问题中,最大的问题就是,当我们匹配失败的时候,我们不一定要从头再来:
假设我们有字符串s和模式p,假设si,si+1,…si+k的子字符串可以和p0,p1,…pk的子字符串成功匹配,而si+k+1和pk+1无法匹配,我们应该从头再来吗?不,我们应该明白的是,si,si+1,…si+k这个子字符串里面可能包含了p的某个前缀,也就是说我们不应该从头开始匹配。
比如p=aabaacc和s=aabaaabaacc,aabaaa无法和aabaac正确匹配,但aabaaa的最后两个aa是和模式aabaacc的前两个字母重合的,我们应当从第三个字母b开始重新匹配。
我们把模式p单独提取出来,上文提到的aabaaa和aabaacc的关系,其实就是aabaa和aabaacc的关系,也就是说,我们应当统计模式的自匹配,当我们的匹配过程中断的时候,可能只需要从p的一个前缀重新开始就可以了。
next数组的长度和p等长,其中,第k个值应该等于:对p0,p1,…,pk,满足pk-m,pk-m+1,…,pk=p0,p1,…,pm的最大m,而且m不等于k。我们会发现,next[k]在很多无法重现前缀的位置都会是0。
另外,我们要知道的是,如果p0,p1,…,pk,pk+1无法匹配了,那么我们应该检查下一个可能的前缀匹配p0,p1,…,pm,pm+1是否可以成功匹配,如果pm+1和pk+1是不匹配的,没关系,index[m]里保存着更短的前缀。
next数组相当于是一个Pattern,也可以看作自动机的重新实现,而且实现起来非常简单:
// 2. kmp算法,第一次写kmp算法,被算法导论给骗了,写起来真的很简单啊,和算导上的伪代码差不多短
class Solution {
public:
static int kmp(string haystack, string needle) {
// 第一步,找到前缀表
int *next = new int[needle.length()];
next[0] = 0;
for (int i = 1; i < needle.length(); ++i)
{
next[i] = needle[next[i-1]?next[i-1]+1:0] == needle[i] ? next[i-1] : 0;
}
// 第二步,进行匹配
for (int i = 0; i < haystack.length(); ++i)
{
int index = 0;
while(index < needle.length() && i+index < haystack.length() && haystack[i+index] == needle[index]){
index++;
}
if (index == needle.length()){
return i;
}
index = next[index];
i += index;
}
return -1;
}
static int strStr(string haystack, string needle) {
if (haystack.length() < needle.length()) return -1;
if (needle.length() == 0) return 0;
return kmp(haystack, needle);
}
};