问题
KMP算法解决的是两个字符串中一个是否在另外一个中存在以及存在位置的索引的问题
常规思路
遍历长串中的每个位置,从该位置开始与短串匹配,对上了返回结果,直到遍历完长串的所有字符
KMP算法
首先,通过一个辅助数组,记录短串每个位置字符前面的子串的前缀和后缀中相同的前缀和后缀串的最长的长度,挺绕的
- 前缀和后缀:字符串中从前(后)往后(前)取部分构成的子串即字符串的前(后)缀,需要说明的是,前后缀长度小于字符串总长,意味着前缀和后缀不能是原字符串
- 前缀和后缀相同:
然后,定义两个指针a,b,分别指向长串和短串的开头,开始匹配:
如果匹配,同时往后移;
如果不匹配,通过next数组和短串此时的位置获取next[b]的值,a指针往后移,移到next[b]的值的位置,b移回开头,重新匹配;
如果a移到末尾了,说明不存在,可以返回结果
如果b移到末尾了,说明找到了,也可以返回结果
接着想想找每个字符的最长前缀和最长后缀匹配长度的意义
首先遵循常规思路,长串一个一个试与短串匹配,如果短串每个字符都不相容,即没有重复的情况,那么如果我们来做的话,如果遇到不匹配,就可以直接把a指针移到当前位置下一个位置继续匹配,根本不需要试试b-x开始的情况,因为短串各不相同,从这几个位置开始匹配肯定不能匹配成功
那么,如果短串有重复的情况呢
此时,就不应该移到当前位置的下一个位置,而应该往前移最大匹配长度,继续从头开始匹配,这样跳过了gh的尝试,而又没有忽略gh后面abcx这一段的可能性
由此,其实已经可以看出来,求next数组,也就是最大匹配前后缀的长度,其实也就是在求短串的当前字符前面的子串重复的最大长度,而求最大长度的原因也很好理解,为了包含所有可能性,防止漏掉某些可能完成匹配的开始位置
编码
上述流程和编码实现的流程上还有区别,但也仅仅是做了一些优化,本质还是一样
package leetcode.editor.other;
public class KMP {
public static void main(String[] args) {
String a = "abcdefg";
String b = "gh";
int indexOf = getIndexOf(a, b);
System.out.println(indexOf);
}
public static int getIndexOf(String s1, String s2) {
//s1为null或者s2为null,没有找的必要
if (s1 == null || s2 == null)
return -1;
//定s1为长串,s2位短串
if (s1.length() < s2.length()) {
String temp = s1;
s1 = s2;
s2 = temp;
}
//获取next数组
int[] next = getNext2(s2.toCharArray());
//定义两个指针,指到两个串开头
int i = 0, j = 0;
//当i没到长串末尾,j也没到短串末尾,循环
while (i < s1.length() && j < s2.length()) {
//相同字符,同时后移
if (s1.charAt(i) == s2.charAt(j)) {
i++;
j++;
//如果next值为-1,代表此时j在短串开头,同时第一个就不匹配,那么i往后移
} else if (next[j] == -1)
i++;
//否则j移到next位置
else j = next[j];
}
return j == s2.length() ? i - j : -1;
}
public static int[] getNext(char[] array) {
if (array == null || array.length < 2)
return new int[]{-1};
int[] next = new int[array.length];
next[0] = -1;
next[1] = 0;
for (int i = 2; i < array.length; i++) {
int temp = next[i - 1];
while (temp > 0 && array[i - 1] != array[temp]) {
temp = next[temp - 1];
}
next[i] = temp == 0 ? 0 : next[temp - 1] + 1;
}
return next;
}
public static int[] getNext2(char[] array) {
if (array == null || array.length < 2)
return new int[]{-1};
int[] next = new int[array.length];
next[0] = -1;
next[1] = 0;
int cn = 0;
int i = 2;
while (i < array.length) {
if (array[i - 1] == next[cn])
next[i] = ++cn;
else if (cn > 0)
cn = next[cn];
else next[i++] = 0;
}
return next;
}
}
这里不匹配时移动a改为了不动,移动b,移动位置为next[b-1]的值,需要理解的是next数组的每个位置上的值由两层含义,一个next[i]既代表了i位置之前的子串重复的最大长度,也代表了最大前缀串的后一个字符的位置