先上代码
class Solution {
public int strStr(String haystack, String needle) {
int haylen = haystack.length();
int neelen = needle.length();
if(neelen == 0) return 0;
if(haylen == 0) return -1;
char[] hayarr = haystack.toCharArray();
char[] neearr = needle.toCharArray();
return KMP(hayarr,haylen,neelen,neearr);
}
public int KMP(char[] hayarr, int haylen, int neelen, char[] neearr){
int[] next = creatNext(neearr,neelen);
int neePoint = 0;
for(int hayPoint = 0;hayPoint<haylen;hayPoint++){
while(neePoint > 0 && hayarr[hayPoint] != neearr[neePoint]){
neePoint = next[neePoint-1] + 1;
}
if(hayarr[hayPoint] == neearr[neePoint]){
neePoint++;
}
if(neePoint == neelen) {
return hayPoint - neelen + 1;
}
}
return -1;
}
public int[] creatNext(char[] neearr, int neelen){
int[] next = new int[neelen];
//都减去1的目的是让next中的值都对应下标。
int k = -1;
next[0] = -1;
for(int i = 1;i<neelen;i++){
while(k != -1 && neearr[k+1] != neearr[i]){
k = next[k];
}
if(neearr[k+1] == neearr[i]){
k++;
}
next[i] = k;
}
return next;
}
}
先看next数组的实现
上面的代码实现的next数组记录的是当前模式串的对应位置的前面最大相同前缀的末尾所在的索引值。
并不是长度,所以我们需要减去1,即k的初始值为-1.
对于循环的选择,可以选择while,也可以选择for这里选择for循环,有点遍历的意思。因为这样就可以可以把需要做的事情划到每一个模式串的值,这样做无疑比while只有终止条件的那种情况下思路清晰的多。
我们具体来看对应的程序:
对模式串进行next数组的构建,有两种情况:
- 对应的字符相同
- 对应的字符不同
先来看不同的情况,如果不同的话,就需要往前去找,往前找的话可以从头开始找,这样时间复杂度是高的,那么就在原来的基础上,即前一个字符的最大前缀末尾所在位置去找,这样就节省了时间。
然后是相同的情况,如果相同,那么长度就+1,那么记录的next数组对应的值也就+1.
至于为什么是k+1而不是k,我们知道,next[0]的初始值就是k的初始值+1,next[0]为-1表示所在前缀的下标是-1(这是人为规定的,也就是没有,但是具体也是有意义的,看下面)。这里我们操作的指针就是最大前缀的末尾的下一个字符,所以需要+1,而在需要向前回溯的时候,需要的就是看上一个字符,所以不+1,但是这里的回溯也是有限制的,当回溯到最前面的时候,就需要停止回溯,那么回溯到最前面如果没有条件限制会发生呢?数组下标为-1,超出数组的界限,这就是所谓的边界条件。
再看KMP的主体部分
同样的,我们需要对每一个遍历到的主串的字符进行处理,所以还是用for魂环,这样边界清晰:不需要在while循环里面添加if判断语句,思路也清晰。
当两个指针指向的字符不同的时候,根据KMP算法,移动模式串的使得前缀到后缀的位置,然后比较前缀下面的字符,所以就是程序中所示。