KMP算法解决的问题
字符串str1和str2,str1是否包含str2,如果包含返回str2在str1中开始的位置。
如何做到时间复杂度O(N)完成?
最长前缀后缀匹配长度k:
k一定小于字符串长度,因为k==字符串长度时就是比较两个字符串本身,一定相等,没有意义
求str2的next数组:
next[i]表示str[0]~str[i-1]的最长前缀后缀匹配k。由于next[0]前面没字符串,人为规定next[0]=-1
经典过程:
如果str1和str2前面都相等,str1比较到最后是X,str2比较到最后时Y,不相等:
str1由X位置跳回到i+1位置,str2由Y位置跳回到0位置
KMP:
如果str1和str2前面都相等,str1比较到最后是S,str2比较到最后是Z,不相等:
str1位置不变,还指向S,str2指向str2[ next[最后] ]
两个关键点:
1)此时str1以 i ~ j - 1 范围为起点配str2是一定配不出来的
2)此时实际上是str1以j为起点,str2以0为起点开始配,但是因为三个框字符串相同,因此str1位置不变,还指向结尾,str2指向str2[ next[最后] ]
证明关键点1):
假设此时str1以 i ~ j - 1 范围中有位置k,以k为str1的起点,可以配出str2。那么由k到X-1这一段一定与str从0开始的一段相等,也就是说,str2从0开始更长的一段和str2以Y结尾更长的一段相等,则str2的位置Y找到了一个更大的最长前缀后缀匹配长度,与已知条件矛盾。
在查找过程中str1一直往前走,str2依据next数组依次往前找,如果在0位置还找不到,str1往前走
时间复杂度:O(N)
求next数组:
动态规划,比较i - 1位置与cn位置,如果相同,next[i] = cn + 1 ; i++ ; cn++ ; 否则cn = next[cn],cn == 0时,next[i] = 0 ; i++;
时间复杂度:O(M) M为str2长度
vector<int> getNext(string s){
vector<int> ans (s.size(), -1);
if(s.size() == 1){
return ans;
}
ans[0] = -1;
ans[1] = 0;
int i = 2;
int cn = 0;
while(i < ans.size()){
if(s[cn] == s[i - 1]){
ans[i] = cn + 1;
i++;
cn++;
}
else if(cn > 0){
cn = ans[cn];
}
else{
ans[i] = 0;
i++;
}
}
return ans;
}
int kmp(string str1, string str2){
if(str1.empty() || str2.empty() || str1.size() < str2.size()){
return -1;
}
int i1 = 0;
int i2 = 0;
vector<int> next = getNext(str2);
while(i1 < str1.size() && i2 < str2.size()){
if(str1[i1] == str2[i2]){
i1++;
i2++;
}
else if(i2 != 0){
i2 = next[i2];
}
else{
i1++;
}
}
return i2 == str2.size() ? i1 - i2 : -1;
}