KMP算法解决的问题:字符串str1和str2,str1 是否包含str2,如果包含返回str2在str1中开始的位置。如何做到时间复杂度 O ( N ) O(N) O(N)完成?(经典字符串匹配问题)
暴力解法:遍历str1中的每个字符,判断以该字符为首字符时,能否与str2匹配,时间复杂度为 O ( N ∗ M ) O(N*M) O(N∗M)
不难发现经典解法的实质就是,如果i
位置为首的字符串不匹配,就往回跳到i+1
从头开始判断,仔细看整个过程可以知道,每次回跳,都会重复判断很多字符,那是否可以将这些信息收集利用起来,使得每次不用回跳这么多?KMP算法就是这样的,它甚至不用回跳。
最大相同前、后缀长度:对于一个字符串str的第i
位置的字符str[i]
,其最大相同前后缀长度指的是在这个字符之前的子串(即str[0~i-1]
)所具有的最长相同前后缀的长度,以abbabbk
为例:
k
这个字符,其对应的子串为abbabb
。
a
,后缀为b
,不相同(不匹配❌);ab
,后缀为bb
,不相同(不匹配❌);abb
,后缀为abb
,相同(匹配成功✔);abba
,后缀为babb
,不相同(不匹配❌);abbab
,后缀为bbabb
,不相同(不匹配❌);next数组:就是由最大相同前后缀长度(还挺拗口)构成的数组,该数组长度等于字符串长度,字符串中每个字符的最大相同前后缀长度都记录在该数组中。
举个栗子:
字符串:[ a a b a a b s a a b a a b s t]
next: [-1 0 1 0 1 2 3 ...]
我们有了next数组,就可以根据next数组的信息,来决定匹配开始的位置,具体过程如下:
我们要在str1中查找str2,就计算出str2的next数组。
假如现在从str1[i]
位置开始匹配,匹配到str1[X]
位置发现不相等,即str1[X]≠str2[Y]
,由str2的next数组我们可知Y位置之前字符串的最大前后缀匹配长度,也就是下图中的橙圈,既然前后缀相同,我们就可以直接省略str2前缀的匹配(即从位置j开始的匹配),直接从可能不相等的位置开始验证(即判断str1[X]
和str2[X-j]
是否相同)。
如何认定以[i, j]
范围上任意一个字符为首的字符串一定无法与str2匹配呢?
如何求next数组?
public class Kmp {
/**
* @param s 在哪个字符串中匹配
* @param m 要匹配的字符串
* @return 首个匹配字符串的首字符下标。如果没有匹配,则返回-1
*/
public static int getIndexOf(String s, String m) {
// 要求 N >= M, N = s.length, M = m.length
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, i2 = 0;
// O(M)
int[] next = getNext(str2);
// O(N)
while (i1 < str1.length && i2 < str2.length) {
if (str1[i1] == str2[i2]) {
i1++;
i2++;
} else if (i2 > 0) {
// 不匹配,i2往前跳
i2 = next[i2];
} else {
// str2中比对的位置已经无法往前跳了
i1++;
}
}
// i2越界,说明匹配成功;i1越界,说明匹配失败;(同时越界也代表匹配成功)
return i2 == str2.length ? i1 - i2 : -1;
}
/**
* 获取ms的next数组
*/
public static int[] getNext(char[] ms) {
if (ms.length == 1) {
return new int[]{-1};
}
int[] next = new int[ms.length];
next[0] = -1;
next[1] = 0;
// i: next数组的位置
int i = 2;
// cn: 要和next[i-1]比较的位置
int cn = 0;
while (i < next.length) {
if (ms[i - 1] == ms[cn]) {
next[i++] = ++cn;
} else if (cn > 0) {
// 当前跳到cn位置的字符,和i-1位置的字符配不上,则cn继续向前
cn = next[cn];
} else {
next[i++] = 0;
}
}
return next;
}
public static void main(String[] args) {
System.out.println(getIndexOf("abbsabb", "sabb")); // 3
}
}
复杂度分析:
i
和i - cn
i
增加,i - cn
不变i
不变,i - cn
加一i
和i - cn
都增加[0, M]
,所以整体复杂度为 O ( 2 M ) = O ( M ) O(2M)=O(M) O(2M)=O(M)i1
和i1 - i2
i1
增加,i1 - i2
不变i1
不变,i1 - i2
增加i1
和i1 - i2
都增加[0, N]
,所以整体复杂度为 O ( 2 N ) = O ( N ) O(2N)=O(N) O(2N)=O(N)LeetCode原题:28. 找出字符串中第一个匹配项的下标 - 力扣(LeetCode)
难度:Medium
有一说一,左神讲KMP算法讲的是真的好,不过可能我明天就忘了_(:3」∠)_,还是需要不断巩固啊