先引出KMP的概念,以下引用自wikipedia:
在计算机科学中,Knuth-Morris-Pratt
字符串搜索算法(或KMP算法)通过采用以下观察方法来搜索W主“文本字符串”中“单词”的S出现:出现不匹配时,单词本身就包含了足够的信息。确定下一个匹配项从何处开始,从而绕过对先前匹配的字符的重新检查。
…
这是第一个用于字符串匹配的线性时间算法。
场景:找出字符串s1中,字符串s2的出现位置。
例题:28. Implement strStr()
Implement strStr().Return the index of the first occurrence of needle in haystack, or -1 if needle is not part of haystack.Example 1:Input: haystack = “hello”, needle = “ll”
Output: 2
Example 2:Input: haystack = “aaaaa”, needle = “bba”
Output: -1
Clarification:What should we return when needle is an empty string? This is a great question to ask during an interview.For the purpose of this problem, we will return 0 when needle is an empty string. This is consistent to C’s strstr() and Java’s indexOf().
思考:
假设s1有n个字符,s2有m个字符:
1、可以用蛮力算法,设置两层循环,外层遍历s1的每个字符s1[i],内层比较从s1[i]开始的字符串和s2从s2[0]开始的每个字符。最差情况下(如s1= “aaaaaaaa”,s2 = “aab”),每次比较需要比较到s2的最后一个字符,复杂度为O(mn)。代码大致如下:
for (int i = 0; i < (s1.size() - s2.size()); i++) {
int j = 0;
for (; j < s2.size(); j++) {
if (s1[i + j] != s2[j])
return -1;
}
if (j == s2.size())
return i;
}
2、使用KMP算法,用O(m)的建表时间和O(n)的比较时间。
分析:方法1中我们可以将上一次的比较结果用于下一次比较。举几个例子:
(1)s1 = “ababaaa”,s2 = “abc”,第一次循环时,(s1[2] = ‘a’) != (s2[2] = ‘c’),这个时候我们不仅得到了s1[2] != s2[2],显然还可以知道:我们第二次循环可以从i = 2开始比较。为什么知道可以从i =2开始比较呢?因为:s2[1] = s1[1] = ‘b’ != (‘a’ = s2[0]),s1[1]不是‘a’。
(2)S = “ABDAABDABD”,W=“ABDAB”,第一次循环时,有:
第二次循环,我们可以从i = 4,j = 1开始比较,为什么呢?因为:
- 在W中,已比较的字符串中的 后缀 与 前缀 最长的相同部分是’A’,长度为1;
- 而要找到完全相等的字符串,字符串的开头一定相等。
- 已经比较的字符串w1(‘ABDA’)中,与字符串开头(即前缀)相等的部分w-p,如果是出现在w1的末尾处w-s(即后缀),则下次比较时,可以直接从w-p的下一位(B)开始比较。如下图所示:
现在我们需要用一个表来存W的从0开始的子串的最长相等前缀后缀的长度,使得第3步容易进行,其代码如下:
(这里的 len = lps[len - 1],可以用"aabaaac"来推导,在判断第五个"a"的时候就可以较好地理解为什么要这么写了)
vector kmpProcess(string w) {
int n = w.size();
vector lps(n, 0);
for (int i = 1, len = 0; i < n; ) { //len : 最长相等前缀后缀的长度
if (w[i] == w[len]) {
len++;
lps[i] = len; // 从下标0到下标i的子串的最长相等前缀后缀的长度为len
i++;
}
else {
if (len) { //len是前一个子串的最长相等前缀后缀的长度
len = lps[len - 1]; //获得长度为len的前缀的
//最长相等前缀后缀的长度,
//减短字符长度,放宽要求,看看能不能匹配到
}
else { //从i开始的子串的第一个字符就与w开头不一样,len = 0
lps[i] = 0;
i++;
}
}
}
return lps;
}
处理完数据后,对应的查找函数也应该修改为如下:
int stStr (string s, string k) {
int m = s.size(), n = k.size();
if (!n) {
return 0;
}
if (m < n) {
return -1;
}
vector lps = kmpProcess(k);
for (int i = 0, j = 0; i < m;) {
if (s[i] == k[j]) {
i++; j++;
if (j == n) {
return (i - j);
}
}
else {
if (j) {
j = lps[j - 1]; // 执行步骤三,利用已经比较的结果
}
else {
i++; // j = 0,从一开始就不一样,从下一个i开始比较
}
}
}
}
以上,作为KMP的思路的整理。