KMP 算法个人理解
编辑
48
2024-05-16
首先先把这个不到九分钟的视频看一遍:
无论是暴力匹配还是 KMP 算法,其实都是双指针算法。
主串指针为 i
,子串指针为 j
。
但是区别在于,暴力匹配的时候,主串指针会往前回退,但 KMP 匹配的时候,主串指针永远不会往前回退,因此 KMP 的效率更高。
尽管子串指针会向前回退,但是子串指针永远只跟着主串指针向后移动。因此时间复杂度为 O(n)
。
其核心就是找相同的前后缀,也就是 next
数组,如果在已经匹配成功的部分中,当前的后缀有一个一模一样的前缀,那么我们下次匹配的时候,就可以直接将这个后缀当作已经匹配好的前缀来使用(看视频的动画更容易理解)。
现在问题变为:在子串中,指针指向的每一个字符作为后缀的一部分,计算出其长度最长的前缀,这个问题就是代码中 next
数组的求解,可以用动态规划解决。
完整代码
public class KMPAlgorithm {
// KMP 字符串匹配算法
public static int kmpSearch(String string, String patt) {
int[] next = buildNext(patt); // 计算 next 数组
int i = 0; // 主串指针
int j = 0; // 子串指针
while (i < string.length()) {
if (string.charAt(i) == patt.charAt(j)) {
// 字符匹配,指针后移
i++;
j++;
} else if (j > 0) {
// 失配,根据 next 数组回溯
j = next[j - 1];
} else {
// 子串第一个字符就失配
i++;
}
// 完全匹配
if (j == patt.length()) {
return i - j; // 返回匹配起始位置
}
}
return -1; // 未匹配返回 -1
}
// 计算 next 数组
private static int[] buildNext(String patt) {
int[] next = new int[patt.length()];
next[0] = 0; // 初始化第一个元素
int prefixLen = 0; // 共同前后缀长度
int i = 1;
while (i < patt.length()) {
if (patt.charAt(prefixLen) == patt.charAt(i)) {
prefixLen++;
next[i] = prefixLen;
i++;
} else {
if (prefixLen == 0) {
next[i] = 0;
i++;
} else {
prefixLen = next[prefixLen - 1]; // 回溯
}
}
}
return next;
}
// 测试代码
public static void main(String[] args) {
String string = "ababcabcacbab";
String pattern = "abcac";
int index = kmpSearch(string, pattern);
System.out.println("匹配位置: " + index);
}
}
next 数组计算
这张图是理解 next 数组计算的关键
LeetCode 练习题
- 0
- 0
-
分享