KMP
kmp算法解决的问题
字符串str1和str2,str1是否包含str2,如果包含返回str2在str1中开始的位置,并且要求做到时间复杂度为O(N)
注:str2必须为str1的连续子串
暴力解法:尝试str1的每一个开头
极端例子
这种情况时间复杂度就比较高,此时时间复杂度为O(n*m)
KMP的过程与暴力解决的过程相似,但是过程有优化(加速),返回的结果的是str2在str1中开始的位置,如果不包含就返回-1
字符k前面的字符串的前缀和后缀
k带的信息是前缀和后缀相等的最大长度(长度为前缀总长度的时候不计算)
str2的所有字符都需要此信息
如果一个字符前面没有字符串就人为规定为-1
字符串str2的0位置信息为-1,1位置的信息为0
按照暴力递归的思路:一开始从str1的i位置开始,从str2的0位置开始,一直匹配,直到匹配不成功,此时i来到了s字符,按照原来暴力递归的思路,此时应该i回到i+1位置,str2还是0,再重新开始匹配
但KMP怎么操作:i来到s字符之后,停住,str2从nextArr数组中获取字符z的信息,得到结果为6,str2从索引为6即字符s开始,二者再从当前的两个位置开始重新匹配
两件事情:
str1的j位置到s之前的这段字符串与str2的相同位置是相等的,而从nextArr的原理可以得知前缀和后缀相等,即此时str1从s字符开始,str2从索引为6的字符s开始匹配,前面的一段字符串是相等的,减少比较的次数(直接来到可能不相等的位置)
如何证明从str1的最开始的i位置到j之前的任何一个字符开始都没法与str2匹配?
使用反证法,假设i到j位置之间的任意一个位置为k(j位置是根据第一个不匹配的字符的最长前缀和后缀相等的数量往回推出来的),假设str1从k位置开始可以把整个str2匹配出来,即str1从k到x之前的字符串与str2从0到相等数量的字符串是一样的,而经过第一次的匹配可以得知str1从k到x之前的字符串是与str2相同位置是一样的(即得到y信息的后缀),而如果这两个条件都成立,则说明y的信息有变(nextArr数组的信息正确),最长前缀和后缀相等的字符串数量变长了,而这是不可能发生的,因此假设失效,即str1从i到j位置的任意一个位置k都不能把str2整个匹配出来
如果一直匹配到str2不能再往前了都没法匹配成功,str1的比对位置往下一个
getNextArray方法:
第一位是-1
第二位是0
第三位根据前两位是否相等来判断
后面的根据前面的计算结果来判断:(dp)
- 此时到达的位置是否与前一位的信息(数量),即前缀的下一位是否相等(标记该位置为k),相等则+1就是这一位的信息
- 不相等的话看k位的前缀的下一位,判断是否相等,相等的话就是k位的信息+1,如果还是不等就一路往前跳,跳到最前面的话信息就是0,(注意这里还是要比较第一个字符和此时的字符是否相等,是的话返回1,不相等的话再返回0)
public int getIndexOf(String s,String m){
if(s==null||m==null||m.length()<1||s.length()<m.length()){//注意最后这个判断条件
return -1;
}
char[] str1=s.toCharArray();
char[] str2=m.toCharArray();
int i1=0;
int i2=0;
int[] nextArr=getNextArray(str2);//O(M)
while(i1<str1.length&&i2<str2.length){//O(N),m<n,所以整个kmp算法的时间复杂度为O(N)
if(str1[i1]==str2[i2]){
i1++;
i2++;
}else if(i2==0){//nextArr[i2]==-1
i1++;
}else{
i2=nextArr[i2];
}
}
//i1越界或者i2越界了 i2越界代表str1的某个开头匹配出整个str2了 i1越界就代表整个str1没有能匹配出str2的
return i2==str2.length?i1-i2:-1;//i1-i2即str1的索引回到q
}
public int[] getNextArray(char[] ms){
if(ms.length==1){
return new int[]{-1};
}
int[] next=new int[ms.length];
next[0]=-1;
next[1]=0;
int i=2;//当前位置
int cn=0;//拿哪个位置和当前位置比较,也代表当前使用的信息是多少
while(i<next.length){//这里的时间复杂度估计和主方法中的while一致
if(ms[i-1]==ms[cn]){
next[i++]=++cn;
}else if(cn>0){//当前跳到cn位置的字符,和i-1位置的字符匹配不上
cn=next[cn];
}else{
next[i++]=0;
}
}
return next;
}
怎么估计时间复杂度
人为的分析两个变量
两个变量最大的变化幅度就是循环发生的次数
三个循环分支对应的两个变量的变化程度如图
两个变量变化的最大幅度是2N的幅度,而且三个分支都没有让这两个变量下降的情况,所以循环的次数就和这两个变量变化的幅度绑定在一起
所以整个while循环的次数不会超过2N次
即时间复杂度为O(N)