问题提出
- 字符串的模式匹配 是字符串中最重要的操作之一
- 给定两个字符串 一个是 模式串A 一个是 主串B
- 求解:模式串 在主串中出现的位置
暴力匹配
假设主串的长度为 m 模式串的长度为 n 则 最坏情况下的时间复杂度为 O(mn)
class Solution {
public int kmp (String S, String T) {
int lenS = S.length();
int lenT = T.length();
int ans = 0;
int i = 0;
while (i < lenT) {
int j = 0;
int pos = i;
while (j < lenS && pos < lenT && T.charAt(pos) == S.charAt(j)) {
pos++;
j++;
}
if (j == lenS) {
ans++;
}
i++;
}
return ans;
}
}
KMP模式匹配
KMP 算法全称Knuth Morris Pratt,是由 D.E.Knuth、J.H.Morris 和 V.R.Pratt 三位大佬一起捣鼓出来的
KMP 算法目的是为了快速的从主串中找到模式串,强调的是快,那咋快的呢?
那肯定是去掉暴力模式匹配中的”无脑“的部分
算法核心
模式串在匹配主串的过程中 当发现不匹配的时候——去寻找之前已经匹配过的部分中,模式串的前后缀,而且是最长公共的前后缀 进行滑动。
如上图,匹配到 Z 不等于 S
- 寻找 前面已经匹配过的部分的最长前缀
- 下一次匹配 比较字符 Z 与 C
- 减少了无意义的暴力匹配过程
所以我们需要去知道模式串已经匹配过的部分的位置,KMP算法使用一个数组保存位置信息
如何构建记忆数组
- 以字符串 ababc 为例,字符串长度为5,所以构建的记忆数组的长度也为5
- 以字符串 ababccccc 为例,更加深入理解 记忆数组 的构建逻辑 原始字符串 ababccccc 对应记忆数组 001200000
- 为什么字符串后面全是 c 结果对应位置记忆数组的值全是0?
- 连续若干个 c 可以被认为一个 c 字符,出现一个不匹配,那么此时应如何移动?
- 如下图 匹配到 字符 s 的时候,发生不匹配,这是最佳方案应是 第一个字符 a 与 s进行匹配
- 所以,字符 c 对应记忆数组的位置应是0
构建记忆数组代码
Java
public int[] getNext(String S) {
int n = S.length();
// 后缀匹配指向
int i = 0;
// 前缀匹配指向
int j = -1;
// 初始化 next 数组
int[] next = new int[n];
// 此处 next[0] = -1,所以只需要求剩下的 len(T)-1 个即可
while (i < n-1) {
// j == -1 无法匹配 or 匹配成功
if(j == -1 || s.charAt(j) == s.charAt(i)) {
i++;
j++;
next[i] = j;
} else {
j = next[j];
}
}
return next;
}
python
def get_next(s):
i = 0
j = -1
next_arr = [-1] * (len(s)+1)
while i < len(s):
if j == -1 or s[i] == s[j]:
i += 1
j += 1
next_arr[i] = j
else:
j = next_arr[j]
print(next_arr)
解题code
def for_next(s: str) -> list:
i = 0
j = -1
# 这里初始化-1,是为了保证 next_arr[0] = -1
# 长度 les(s) + 1 防止下标越界
next_arr = [-1] * (len(s) + 1)
# i 是 右指针
while i < len(s):
# j == -1 :表示初始化情况和当前字符匹配失败的情况
if j == -1 or s[i] == s[j]:
i += 1
j += 1
# 如果匹配成功,i++ j++
next_arr[i] = j
else:
# j 回到 -1 or 回到未匹配成功字符上一次出现的位置
j = next_arr[j]
print(next_arr)
return next_arr
def for_kmp(s: str, t: str) -> int:
_next = for_next(s)
# 模式串 双指针匹配 目标串
i = j = ans = 0
while i < len(t):
if j == -1 or s[j] == t[i]:
i += 1
j += 1
else:
j = _next[j]
if j == len(s):
ans += 1
j = _next[j]
return ans