KMP算法
1. KMP算法原理
自己读了一遍,发现好多只有自己能看懂。散了吧
1.1 next[]
给定一个数组arr,求它的next[]数组
含义:next[i]表示数组arr第i个元素之前的字符串中,最长的公共前缀后缀的长度。
注:next[0] = -1
next[1] = 0
例子
char[] arr = new char[]{'a', 'b', 'a', 'b', 'c', 'a', 'b', 'a', 'b', 'a', 'k'};
i=3时,arr第3个元素之前的字符串中,最长前缀为a
,next[3]==1
-
如果继续往下判断,即判断
next[4]
的值为多少,只需判断当前最长前缀的下一个元素是否与arr[3]
相等,而最长前缀的下一个元素的下标为next[i-1]
,用代码表示即为arr[next[i - 1]] == arr[i -1] -
假设要判断
next[9]
的值,此时next[9-1]==3
,arr[9-1]=='b'
,根据上述分析,需要判断arr[next[i-1]]
是否与arr[i-1]
相等,即arr[3]
是否等于arr[8]
,相等,则说明i == 8时的最长前缀再加上一个后面的字符,构成了i == 9的最长前缀。 -
假设要判断
next[10]
的值,现已构成的最长前缀为abab
,next[9]==4
,看arr[4]
是否等于arr[9]
发现不等于,且arr[4]
前面还有字符,可以继续向前比较。接下来找倒数第二长前缀,倒数第二长前缀为
ab
,其下一个元素为arr[2]
,接下来比较arr[2]
是否等于arr[9]
发现相等,则next[10]
为倒数第二长前缀的长度+1。 -
如果倒数第二长前缀也不满足,还继续向前找,直到发现第一个元素也不与它相等,则其next值为0。
求next数组的代码如下
public static int[] nextArr(char[] str){
int len = str.length;
if(len == 1){
return new int[]{-1};
}
int[] next = new int[len];
next[0] = -1;
next[1] = 0;
//求next[i],看str[i-1]与str[next[i-1]]是否相等,不等,往前判断
//刚开始时,i==2,next[i-1]==0,故设置cn=0;
int cn = 0;//最长前缀的下一个元素的下标。
//先跟最长前缀的下一个元素比较,不相等,再跟第二长前缀的下一个元素比较......
for(int i = 2; i < len; i++){
if(str[i - 1] == str[cn]){
cn++;
next[i] = cn;
i++;
}else if(cn > 0){//即使不匹配,但是当前位置不是数组的首部,说明前面还有字符,继续比较
//这里为什么不设置判断条件为cn>=0? 因为如果cn==0,在上面的if语句中,
//判断过str[i-1]!=str[0],说明字符串第一个字符都不与str[i-1]相等,直接得出next[i]==0,
cn = next[cn];
}else{//cn==-1,说明前面不可能有字符与str[i-1]相等了
next[i] = 0;
i++;
}
}
return next;
}
1.2 算法流程
为每个字符串设置一个遍历指针,从头依次开始比较
如下例,当i=4,j=4时,对应的元素不相等。
此时next[j]==2
,只需要arr2向后移动,让j跳到next[j]
的位置上
此时依旧不相等。j继续向前跳。由j=next[j]
得出j值为0。故arr[4]与arr2[0]比较,还不相等。由j=next[j]
得出j值为-1。说明arr2的第一个元素与arr[4]不匹配,只能再看arr2的第一个元素与arr[5]匹配不匹配
下面再举另外一个例子。如果next[j]==-1
,说明arr2的第一个元素都不匹配,arr2向后移动1,即i++
,
如下图例子所示
然后一直往后移动,直到i=4,j=4
时,才第一次匹配成功
代码
public static int function(String str1, String str2){
char[] arr1 = str1.toCharArray();
char[] arr2 = str2.toCharArray();
int[] next = nextArr(arr2);
int len1 = str1.length();
int len2 = str2.length();
int i = 0, j = 0;//i遍历arr1, j遍历arr2;
while(i < len1 && j < len2){
if(arr1[i] == arr2[j]){//arr2中的元素与arr1中的相匹配,都不移动,接着向下遍历
i++;
j++;
}else{
if(next[j] == -1){//arr2中第一个元素与arr1不匹配,只有arr2向后移动
i++;
}else{//arr2不是向右移动1,那是笨方法,而是使j=next[j],让j向后跳
j = next[j];
}
}
}
return j == len2 ? i - j : -1;
}
1.3 完整代码
public class Solution_KMP {
public static int function(String str1, String str2){
char[] arr1 = str1.toCharArray();
char[] arr2 = str2.toCharArray();
int[] next = nextArr(arr2);
int len1 = str1.length();
int len2 = str2.length();
int i = 0, j = 0;//i遍历arr1, j遍历arr2;
while(i < len1 && j < len2){
if(arr1[i] == arr2[j]){//arr2中的元素与arr1中的相匹配,都不移动,接着向下遍历
i++;
j++;
}else{
if(next[j] == -1){//arr2中第一个元素与arr1不匹配,只有arr2向后移动
i++;
}else{//arr2不是向右移动1,那是笨方法,而是使j=next[j],让j向后跳
j = next[j];
}
}
}
return j == len2 ? i - j : -1;
}
public static int[] nextArr(char[] str){
int len = str.length;
if(len == 1){
return new int[]{-1};
}
int[] next = new int[len];
next[0] = -1;
next[1] = 0;
//求next[i],看str[i-1]与str[next[i-1]]是否相等,不等,往前判断
//刚开始时,i==2,next[i-1]==0,故设置cn=0;
int cn = 0;
for(int i = 2; i < len; i++){
if(str[i - 1] == str[cn]){
cn++;
next[i] = cn;
i++;
}else if(cn > 0){//即使不匹配,但是当前位置不是数组的首部,说明前面还有字符,继续比较
//这里为什么不设置判断条件为cn>=0? 因为如果cn==0,在上面的if语句中,判断过str[i-1]!=str[0],说明字符串第一个字符都不与str[i-1]相等,直接得出next[i]==0,
cn = next[cn];
}else{//cn==-1,说明前面不可能有字符与str[i-1]相等了
next[i] = 0;
i++;
}
}
return next;
}
public static void main(String[] args){
//......
}
}
2. KMP算法应用
2.1 给定一个字符串,构造另一个字符串
题意:给定一个字符串,往该字符串后面添加元素,使得最后的字符串包含两个不同的原字符串,且要求要添加的元素最少
例子:原字符串:abcabc
,在其后再添加一个3个元素,分别是a
,b
,c
,最终得到的字符串为abcabcabc
。改字符串相等的最长前缀和最长后缀为abcabc
,即原字符串。
思路:求原字符串的next数组,假设此数组长度为n,且该数组最长前缀与后缀长度为len,从最长后缀开始,补齐原字符串即可。
如原字符串abcdeab
,此时相等的前后缀长度为2,后缀为ab
,则再补上cdeab
即可。
2.2 判断一棵树是否为另一棵树的子树。
题意大概是如下的样子
思路:将两棵二叉树序列化,然后判断一个是否为另一个的子串。