KMP算法
1.KMP算法解决的问题
字符串str1和str2,str1是否包含str2,如果包含返回str2在str1中开始的位置,如果不包含返回-1。如果做到时间复杂度O(N)完成?
测试用例:str1 = “ABC1234de”, str2 = “1234”.返回3。
1.1最大相等前后缀
如一个序列abbabbk,求字符k的最大相等前后缀。
因此,序列abbabbk中k的最大相等前后缀长度为3。注意:
- 前后缀是k之前的序列的前后缀不包括k。
- 最大相等前后缀长度不能是前后缀的整体,这样没有意义。
1.2next数组
对str2的每一个字符都求它位置之前的字符子串的最大相等前后缀长度,形成了一个next数组。
如:str2 = "aabaabs",求str2的next数组
- **下标0位置之前没有子串,**没有前后缀,因此认为规定next[0] = -1。
- **下标1位置有子串a,**前后缀只有a,但最大相等前后缀长度不包括整个前后缀,因此认为规定next[1] = 0.
- 下标2位置有子串aa,前后缀有a,因此2位置的最大相等前后缀为1。
- 以此类推。
1.3KMP匹配过程
如:str1 = “abbstkscabbstks”, str2 = “abbstkscabbstkz”
尝试str1从0位置,str2从0位置,str1是否能匹配str2,当来到下标14位置时才第一次出现str1[14] != str2[14]。
如果是经典过程(暴力双循环)则会来到str1从下标1位置,str2继续从0位置来判断str1能否匹配str2。而KMP加速过程如下:
str2在下标14位置的最大相等前后缀长度为6,则str2会跳到下标6位置开始与str1的14位置字符继续比较。
这个匹配过程有两个实质:
-
其实就相当于str1来到8的j位置(14 - 6)与str2来到0位置开始比较str1是否能匹配出str2,只不过str2中z的最长相等前后缀长度为6,0~5位置的字符不用比较就能知道相等。
-
从i到j中间任何一个位置都不能匹配出str2。证明如下:
(1)假设i和j任意一个位置k能匹配出整个str2。
(2)str1(k….14 - 1)可以与str2(0…14 - k - 1)匹配。
(3)但是str1(k…14 - 1)与str2(k….14 - 1)相同。
(4)也就是说str2(0…14 - k - 1)与str2(k…14 - 1)相同,也就是说str2的最大相等前后缀长度为k。然而k < j,我们已经求出str2的最大相等前后缀长度为14 - j ,假设这种情况的最大相等前后缀为14 - k > 14 - j,也就是说如果k位置能够匹配出str2,那么str2最大相等前后最长度为14 - k > 14 - j,超出str2本来的最大相等前后缀长度,前后矛盾,因此假设不成立。
1.4next数组求法
- next[0]人为规定为-1,next[1]认为规定为0,next[2]看str2[0]和str2[1]是否相等,相等则next[2] = 1,否则next[2] = 0。
- next[3]可以根据next[0]、next[1]、next[2]求出;next[4]可以根据next[0]、next[1]、next[2]、next[3]求出…依次类推
- 假设来到i位置,可以知道next[i - 1]的位置k,如果str2[k] == str2[i],则next[i] = next[i - 1] + 1。否则跳到next[k]位置,如果str2[next[k]] == str2[i],则next[i] = next[i] = next[k] + 1。依次类推。有点抽象,看如下跳转的例子。
【例子】str2 = abbstabbecabbstabb?。?为可变字符。
1.5KMP代码
vector<int> getNext(const string &s) {
if (s.size() == 1) {
return {-1};
}
vector<int> next(s.size());
next[0] = -1;
next[1] = 0;
int i = 2; //next数组位置
int cn = 0; //用哪个位置的字符和i - 1位置的字符比,也代表当前使用的最大相等前后缀长度是多少
while (i < next.size()) {
if (s[i - 1] == s[cn]) {
next[i++] = ++cn;
} else if (cn > 0) { //当前跳到cn位置的字符,和i - 1位置字符配不上
cn = next[cn];
} else {
next[i++] = 0;
}
}
return next;
}
int strStr(string str1, string str2) {
if (str1.size() < str2.size() || str2.size() == 0) {
return -1;
}
int i1 = 0, i2 = 0;
//得到str2的next数组
vector<int> next = getNext(str2);
while (i1 < str1.size() && i2 < str2.size()) {
if (str1[i1] == str2[i2]) {
++i1;
++i2;
} else if (next[i2] == -1) { //i2 == 0,无法向前跳
++i1;
} else {
i2 = next[i2];
}
}
//i1越界或者i2越界
return i2 == str2.size() ? i1 - i2 : -1;
}