记录一下kmp算法,以及对于求next数组代码的理解,方便日后复习,适合对kmp算法有一定了解的人食用
学会kmp就可以不使用暴力解法来解决掉这个简单题
28
先进行一些约定
- 前缀:一个字符串中,包含首字母,但是不包含尾字母的子串
- 后缀:一个字符串中,不包含首字母,但是包含尾字母的子串
- s串:文本串
- p串:模式串
为什么要使用kmp
- 对于28题,如果暴力求解的话,每次遇到不匹配的情况,就需要退回到最初的起点进行重新匹配。但是当在某位置不匹配说明了在这个位置之前是都匹配上了,如果回到最初的起点,就没有利用到这个信息,因此三个大佬(K、M、P)搞出来这个算法,目的就是遇到不匹配的情况的时候,不用回退到最初的起点,而是退到合适的位置在开始。
- 要想实现这个想法,就需要引出这个next数组
- next数组与之前提到的前后缀息息相关,同时这个数组和p串紧密相连但是和s串毫无瓜葛
- next数组长度和p串长度相等,它的作用是:当我们在进行匹配时,假设匹配到p串的第j个字母的时候不匹配的,这时候我们就去next数组里找next[j-1],它会告诉我们退回到那个地方重新开始
- 那么next数组里存的到底是什么呢? 举个例子,next数组中下标为j中存放的是p串中,从开头到这个下标(包含)为止的子串中的最大公共前后缀的长度
- 在细致点:p串为:abcdabce
- 下标为6(对应的p串子串为abcdabc)
- 这个子串的前缀有:a、ab、abc、abcd、abcda、abcdab
- 这个子串的后缀有:c、bc、abc、dabc、cdabc、bcdabc
- 很明显,最大公共前后缀的长度为3(abc),因此next[6] = 3
求解next数组代码分析
public void getNext(int[] next,String s){
int j = 0;
next[0] = 0;
for(int i = 1; i < s.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;
}
}
- 分析如下:(看分析前,再熟悉一下前面的前后缀的概念)
- 第一个字符作为p串的最小子串,自然是没有前后缀的,因此next[0] = 0(进入for前的初始化)
- j = 0,i = 1,此时为p串的前两个字母构成的子串,若为ab,则最大公共前后缀的长度为0;若为aa,则最大公共前后缀的长度为1;在第二种情况下,若第三个字母为a,则最大公共前后缀的长度为2,若为其他,则j变为next[0] = 0。
- 带着大家走了两步for循环,现在进行一下归纳:
- 从最后
next[i] = j
这个赋值语句和next数组中的含义,我们可以直接推断出,j就是代表当前的最大公共前后缀的长度 - 假设当前j = c(当前循环结束,马上开始新一次的循环),在开始判断前,意味着到目前为止,前面的最大公共前后缀的长度为c,也就是指针j之前的和指针i之前的相等的子串,如果指针i和指针j所指的字母相同,那么当前的最大公共前后缀的长度再增长一位,就是if语句中的意思。如果指针i和指针j所指的字母不相同,那我指针j就退回到最大公共前后缀的长度小1的位置上在和指针i的字母进行比较,知道相等或者指针j退回到开头即j = 0;
- 如上就是构建next数组的解读,后续会补一个示意图