考虑一个字符串string = 'baabaccbaabaabac'。
当前下标i = 11,从上图可以看到,i之前的字符串string[:i] = 'baabaccbaab'的最长公共前后缀为'baab',长度为4,我们用一个名为next的数组记录该长度,next[i-1] = 4。
可以看到上图中下标j = 4与下标i = 11处字符相等,我们可以由此推断出字符串string[:i+1] = 'baabaccbaaba'的最长公共前后缀的长度值为5。
int j = next[i-1];
if(s[i] == s[j]) {
next[i] = next[i-1] + 1;
}
那如果i和j处字符不等呢?
此时当前下标来到i = 12的位置,此时i和j处字符不等,上面的代码自然也就不成立了。
想象让j处于i的身份...
既然next[i]不能由next[i-1]直接得到,那么不如试着从next[j-1]下手。
通过上图不难看出next[j-1]即string[:j] = 'baaba'的最长公共前后缀的长度为2,且下标k = 2与下标i = 12处字符相等,我们可以由此推断出字符串string[:i+1] = 'baabaccbaaba'的最长公共前后缀的长度值为3。
int j = next[i-1];
int k = next[j-1];
if(s[i] == s[j]) {
next[i] = next[i-1] + 1;
} elif(s[i] == s[k]) {
next[i] = next[j-1] + 1;
}
按照这种方法,我们可以求出整个next数组的值,next[i]的值表示从0开始以i结尾的字符串的最长公共前后缀的长度。
给出代码如下。
void GetNext(char* str) {
next[0] = 0;
next[1] = str[0] == str[1] ? 1 : 0;
for(int i = 2; i < strlen(str); ++i) {
int j = next[i-1];
while(1) {
if(str[j] == str[i]) {
next[i] = j + 1;
break;
} else {
if(j > 0) {
j = next[j-1];
} else {
next[i] = 0;
break;
}
}
}
}
}
那我们搞来这个next数组是为了啥呢?
我们在一个长串中寻找string = 'baabaccbaabaabac',下图中绿色表示匹配成功,红色表示匹配失败,蓝色表示尚未匹配。
考虑匹配中的一个中间状态如下图。
在字符'a'的位置上匹配失败,我们并没有让短串向右错一个位置继续从头匹配,而是从最长公共前后缀的下一位开始匹配。这就是kmp较暴力匹配来的更快的地方。
在实际写代码的时候,我们需要对next数组稍作修改。
// GetNext()补充
for(int i = strlen(s) - 1; i > 0; --i) {
next[i] = next[i-1];
}
next[0] = -1;
于是我们得到如下图所示的新next数组。
给出代码如下。
int kmp(char* str1, char* str2) {
int i = 0, j = 0;
while(i < strlen(str1) && j < strlen(str2)) {
if(str1[i] == str2[j]) {
++i, ++j;
} else {
if(next[j] == -1) {
++i;
} else {
j = next[j];
}
}
}
if(j == strlen(str2)) {
return i - strlen(str2);
} else {
return -1;
}
}
你可以在leetcode 28.实现 strStr()尝试提交,通过了记得点个赞哦。