KMP Update
This commit is contained in:
@@ -1,16 +1,16 @@
|
||||
# KMP
|
||||
|
||||
[代码随想录](https://programmercarl.com/0028.%E5%AE%9E%E7%8E%B0strStr.html)
|
||||
[Leetcode 28. Find the Index of the First Occurrence in a String](https://leetcode.com/problems/find-the-index-of-the-first-occurrence-in-a-string/)
|
||||
|
||||
KMP 主要用在 pattern 匹配上。
|
||||
|
||||
比如给出一个字符串 s 和一个 pattern ,请找出 pattern 第一次在 s 中出现的下标。
|
||||
比如给出一个字符串 s 和一个字符串 pattern ,请找出 pattern 第一次在 s 中出现的下标。
|
||||
|
||||
## 最长公共前后缀
|
||||
|
||||
前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串;
|
||||
前缀是指**不包含最后一个字符**的所有以第一个字符开头的连续子串;
|
||||
|
||||
后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串。
|
||||
后缀是指**不包含第一个字符**的所有以最后一个字符结尾的连续子串。
|
||||
|
||||
比如字符串 aaabcaaa 的最长公共前后缀是 aaa ,最长公共前后缀长度就是 3 。
|
||||
|
||||
@@ -18,18 +18,50 @@ KMP 主要用在 pattern 匹配上。
|
||||
|
||||
`next[i]` 表示 `s[0...i]` 这个子字符串的最长公共前后缀长度。
|
||||
|
||||
E.g.
|
||||
|
||||
```text
|
||||
s: a a b a a f
|
||||
next: 0 1 0 1 2 0
|
||||
```
|
||||
|
||||
## 基本思路
|
||||
|
||||
`i` 代表 pattern 的前缀结尾,`j` 代表 s 的后缀结尾。
|
||||
`j` 代表 pattern 的前缀结尾,`i` 代表 s 的后缀结尾。
|
||||
|
||||
我们假设 `pattern[0...j]` 和 `s[i-j...i]` 一开始是相等的,但是当 `j` 和 `i` 都自增了 1 之后就不完全相等了,即末尾的 `pattern[j]` 和 `s[i]` 不相同。
|
||||
我们假设 `pattern[0...j]` 和 `s[i-j-1...i]` 是相等的,而 `pattern[0...j+1]` 和 `s[i-j-1...i+1]` 不想等,即末尾的 `pattern[j+1]` 和 `s[i+1]` 不想等。
|
||||
|
||||
但是 `pattern[0...j]` 有一段前缀 `pattern[0...k]` 和 `s` 的一段后缀 `s[i-k...i]` 相同。
|
||||
为了方便起见,我们作如下命名:
|
||||
|
||||
那么我们可以从 `pattern[k+1]` 开始匹配。
|
||||
- `pattern[0...j]` 为 p1
|
||||
- `pattern[0...j+1]` 为 p2
|
||||
- `s[i-j-1...i]` 为 s1
|
||||
- `s[i-j-1...i+1]` 为 s2
|
||||
- `pattern[j+1]` 为char_p
|
||||
- `s[i+1]` 为 char_s
|
||||
|
||||
这个时候我们需要让 `j` 回退到 `k+1` 。那么怎么做呢?
|
||||
那么显然有以下结论:
|
||||
|
||||
实际上 `k+1 == next[j-1]` 。
|
||||
- length(p1) == length(s1)
|
||||
- length(p2) == length(s2)
|
||||
- p1 == s1
|
||||
- p2 != s2
|
||||
- char_p != char_s
|
||||
|
||||
参考 s0028 详细代码实现与注释
|
||||
由于 char_p 和 char_s 不想等,因此我们需要将 j 回退到之前的某个位置重新开始匹配。
|
||||
|
||||
回退到哪里呢?暴力匹配算法是直接将 j 回退到 0 ,然而当 p1 的某个前缀 p1_prefix 和 s1 的某个后缀 s1_postfix 相同时,即 p1_prefix == s1_postfix 时,我们其实就可以跳过这段字符串,直接将 j 放到 p1_prefix 的末尾,然后继续尝试匹配。
|
||||
|
||||
那么现在的问题就是怎么找这个 p1_prefix ?
|
||||
|
||||
由于 p1 == s1 ,因此 s1_postfix 其实就是相同长度的 p1 的后缀 p1_postfix ,也就是说我们之前的假设就可以重新写为 p1_prefix == p1_postfix 。
|
||||
|
||||
看到了吗,其实这就是在找 p1 的最长公共前后缀。
|
||||
|
||||
我们找到最长公共前后缀之后,把 j 挪到 p1_prefix 的末尾就行了。
|
||||
|
||||
如果我们构造出了一个 pattern 的前缀表 next ,那么假设现在 j 指向的是 char_p ,把 j 往前挪的代码就可以写为
|
||||
|
||||
```cpp
|
||||
j = next[j - 1];
|
||||
```
|
||||
|
Reference in New Issue
Block a user