原题
Implement strStr().
Returns the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack.
Update (2014-11-02):
The signature of the function had been updated to return the index instead of the pointer. If you still see your function signature returns a char *
or String
, please click the reload button to reset your code definition.
其实就是strstr的实现啦,找出源字符串中目标字符串第一次出现的index。
最naive的办法就是str与dst一个字符一个字符匹配了,注意不只是dst要回滚,str要回滚,比如src “abababc” dst“ababc”
str的头4个字符与dst是一样的,但是匹配到ababa时底5个字符a与c不匹配,这个时候不能从匹配过后的位置(第6个字符)直接继续,而要从开始匹配后一个位置开始(第二个字符)。
代码就不贴了,太过naive超时。不过看到有人说只要加一个判断剩余源字符串长度是否小于目标字符串长度就可以在leetcode上过,我没试过,因为感觉这种优化没太大的意义。
后来我使用的是KMP算法,算法的来历百度百科上有,据说是K某某、M某某与P某某以前研究出来的算法,所以叫这个名字。这个算法我觉得对我还是有点难理解的,现在把自己的想法好好理一理,看大致是不是这么个意思。
KMP的原理是这个样子的,因为naive算法中最耗时的应该就是src 的回滚了,所以如果有办法能让src不回滚,效率将会大大的提高。比如:当src“ababababababcabab” dst”ababc”时。naive算法中每次判断到dst的第5个字符‘c’时都要回滚,因为判断过的abab中的后一个ab有可能是一个我们要进行匹配的字符串的头部,必须要进行匹配。所以如果我们可以在dst的’c‘出现不匹配的时候,直接将src回滚到ada时就好了。
KMP算法就是这样提高效率的,我们可以在src于dst匹配之前,分析dst,然后建立了一个next的数组,next的长度与dst一样。保存了dst中每个字符当出现匹配失败的时候,src的当前字符应该与dst中哪一个字符比较(即回滚dst)。这样我们就不需要回滚src。进行匹配的代码如下:
for (int i = 0, j = 0; i < strlen(src); i++)
{
while (j > 0 && dst[j] != src[i])
j = next[j];
if (dst[j] == src[i])
j++;
if (j == strlen(dst))
return i - strlen(dst) +1;
}
容易看出KMP的时间复杂度是o(n)的,首先src是不需要回滚的,for循环也就执行了n次。while循环中每次j都在减小,也就是说每次最多也就是执行j次,而j只有在执行一次for循环的时候才会+1,所以j最多获得n次加一,所以while循环平均到每次for循环中也就是o(1)的,所以KMP时间复杂度o(n)无误。
上面的代码应该很好理解,就是每次不匹配时dst都回滚,直到匹配或者或者j=0也就是从头开始判断了。我觉得KMP中如何确定next这个数组是个不太好理解的地方。下面就说说next的生成。
next[j]的定义应该是所有满足dst[0…next[j]] = dst[j-next[j]…j]的最大值。很好理解,每次回滚都要其实都还是要从dst的前缀开始匹配的,只是如果src已匹配的字符串中(如上面的ababa)含有dst (ababc)的前缀,就最大限度的保留(aba),下一次匹配则从dst的第4个字符开始。
下面是由定义写出获取next数组的代码:
for (int i = 0; i < strlen(dst); i++)
{
if (i == 0 || i == 1)
next[i] = 0;
else
{
int j = i - 1;
for (;j > 0; j--)
{
int equal = 1;
for (int k = 0; k < j; k++)
{
if (dst[k] != dst[i - j + k])
{
equal = 0;
break;
}
}
if (equal)
{
next[i] = j;
break;
}
}
if (!j)
next[i] = 0;
}
}
这个就是完全按照定义来的,时间复杂度o(n^3)了,但是应该比较好理解。
还有一种办法,可以o(n)的来得到next数组。因为我们可以从next[0..j-1]中的信息推断出next[j]。
for (int i = 1, j = 0; i < strlen(dst) -1; i++)
{
while (j > 0 && dst[j] != dst[i])
j = next[j];
if (dst[j] == dst[i])
j++;
next[i + 1] = j;
}