题目链接:28. 实现 strStr()
KMP算法的经典应用
-
首先讲暴力求解
两层for循环,外层控制文本串,内层控制模式串,不停进行匹配过程中,如果有一个没有匹配上,那么外层回退到这一轮匹配开始前的后一个(以上一个字符开始匹配不上,就从下一个字符开始匹配),而内层模式串要从头开始匹配,所以指针回退到开始,并重新进行匹配。时间复杂度在 O ( m ∗ n ) O(m * n) O(m∗n),其中n为文本串长度,m为模式串长度。 -
什么是前缀和后缀
- 前缀是指所有以第一个字符开头的且不包含最后一个字符的连续子串
- 后缀是指所有以最后一个字符结尾的且不包含第一个字符的连续子串
注意:后缀仍然是原字符串的子串,要从前往后看,而不是从后往前。
例如字符串aabaaf
中,前缀包括a, aa, aab, aaba, aabaa
,后缀包括f,af, aaf, baaf, abaaf
- 什么是前缀表及前缀表的计算
前缀表要求的是最长相等前后缀。
字符串a
的最长相等前后缀为0(因为只有一个字符,前缀和后缀都为0)
字符串 | 最长相等前后缀 |
---|---|
a | 0 |
aa | 1 |
aab | 0 |
aaba | 1 |
aabaa | 2 |
aabaaf | 0 |
那么就得到了字符串aabaaf
的前缀表010120
- next数组及如何用next数组进行字符串的匹配
这里可以简单的把前缀表当成next数组,即next = [0, 1, 0, 1, 2, 0]
文本串:aabaabaafa
模式串:aabaaf
可以观察到,当文本串匹配到第二个
b
字符时,和模式串的f
字符不匹配,那么找到f
前一个位置对应的next值,根据该值对应的数字回退到相应的模式串下标。这里f前一个字符对应的next数值为2,所以模式串英回退到字符b
,并继续匹配。如果能匹配上就进行下去,反之就要继续回退。
原理:
当前字符(
f
)与文本串字符(b
)不匹配,说明本字符前的一个(或几个)字符(b字符后的aa
)与文本串是能匹配上的,而由于计算了前缀表,这就意味着模式串的前一个(或几个)字符(b字符前的aa
)是能够和文本串匹(第二对)配上的,那么只需要让模式串回溯到aa后的字符b
,并继续与文本串的第二个b
进行匹配。
主要存在以下疑惑:为什么只需要模式串回溯,而文本串不用回溯呢(就像暴力求解的过程中),会不会漏解呢?
分析:
如本例中
aabaaf
匹配到f
处与文本串中aabaab..
的b
字符不相同,但是这同时说明了一个问题(理解关键),即文本串不匹配的那个字符前的一系列字符aabaa
是完全与模式串匹配的(也就是相同的),那么这个回溯的过程其实就可以看成模式串求next的一个等价过程了。如果像暴力法一样,下面一步是将模式串aabaaf
与文本串abaa..
进行匹配,且前面四位能匹配得上(假设,实际上不行),那就说明f
前的a对应下标next值为4(最长相等前后缀为4),而事实上next为2(最长相等前后缀为2),所以将主串后移一位一定匹配不上。这样就能够理解为什么回溯到下标2对应的字符b了。
即我们在求解next时就记录了最长前缀与后缀的信息。而当文本串中某一位与模式串不匹配时,文本串中的前几个字符与模式串完全一致,这部分的文本串当成模式串,模式串回溯的过程就是就是找最长前缀与最长后缀的过程。
class Solution {
public int strStr(String haystack, String needle) {
if (needle.length() == 0) return 0;
int[] next = new int[needle.length()];
getNext(next, needle);
int j = 0;
for (int i = 0; i < haystack.length(); i++) {
while (j > 0 && haystack.charAt(i) != needle.charAt(j)) j = next[j - 1];
if (haystack.charAt(i) == needle.charAt(j)) j++;
if (j == needle.length()) return i - needle.length() + 1;
}
return -1;
}
public void getNext(int[] next, String s) {
//i 指向后缀末尾, j指向前缀末尾,同时j代表着i之前(包括i)的子串的最长相等前后缀长度。
int j = 0;
next[0] = 0;
for (int i = 1; i < next.length; i++) {
while (j > 0 && s.charAt(i) != s.charAt(j)) {
j = next[j - 1];
}
if (s.charAt(i) == s.charAt(j)) j++;
next[i] = j;
}
}
}
参考链接:
代码随想录_KMP_视频讲解