Skip to content

Commit 6d3e4e2

Browse files
committed
update fucking-algorithm
1 parent bed3cf5 commit 6d3e4e2

File tree

75 files changed

+12691
-12352
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+12691
-12352
lines changed

面试和算法/fucking-algorithm/README.md

Lines changed: 319 additions & 255 deletions
Large diffs are not rendered by default.
328 KB
Loading

面试和算法/fucking-algorithm/动态规划系列/LCS.md

Lines changed: 349 additions & 27 deletions
Large diffs are not rendered by default.

面试和算法/fucking-algorithm/动态规划系列/动态规划之KMP字符匹配算法.md

Lines changed: 46 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,14 @@
22

33
<p align='center'>
44
<a href="https://github.com/labuladong/fucking-algorithm" target="view_window"><img alt="GitHub" src="https://img.shields.io/github/stars/labuladong/fucking-algorithm?label=Stars&style=flat-square&logo=GitHub"></a>
5-
<a href="https://appktavsiei5995.pc.xiaoe-tech.com/index" target="_blank"><img class="my_header_icon" src="https://img.shields.io/static/v1?label=精品课程&message=查看&color=pink&style=flat"></a>
5+
<a href="https://labuladong.online/algo/" target="_blank"><img class="my_header_icon" src="https://img.shields.io/static/v1?label=精品课程&message=查看&color=pink&style=flat"></a>
66
<a href="https://www.zhihu.com/people/labuladong"><img src="https://img.shields.io/badge/%E7%9F%A5%E4%B9%[email protected]?style=flat-square&logo=Zhihu"></a>
77
<a href="https://space.bilibili.com/14089380"><img src="https://img.shields.io/badge/B站[email protected]?style=flat-square&logo=Bilibili"></a>
88
</p>
99

10-
![](https://labuladong.github.io/pictures/souyisou1.png)
10+
![](https://labuladong.online/algo/images/souyisou1.png)
1111

12-
**通知:[数据结构精品课](https://aep.h5.xeknow.com/s/1XJHEO)[递归算法专题课](https://aep.xet.tech/s/3YGcq3) 限时附赠网站会员,[新版刷题打卡挑战](https://labuladong.gitee.io/algo/challenge/) 上线!另外,建议你在我的 [网站](https://labuladong.github.io/algo/) 学习文章,体验更好。**
12+
**通知:[新版网站会员](https://labuladong.online/algo/intro/site-vip/) 即将涨价;已支持老用户续费~另外,建议你在我的 [网站](https://labuladong.online/algo/) 学习文章,体验更好。**
1313

1414

1515

@@ -21,7 +21,11 @@
2121

2222
**-----------**
2323

24-
> tip:阅读本文之前,建议你先学习一下另一种字符串匹配算法:[Rabin Karp 字符匹配算法](https://labuladong.github.io/article/fname.html?fname=rabinkarp)
24+
::: tip
25+
26+
阅读本文之前,建议你先学习一下另一种字符串匹配算法:[Rabin Karp 字符匹配算法](https://labuladong.online/algo/practice-in-action/rabinkarp/)
27+
28+
:::
2529

2630
KMP 算法(Knuth-Morris-Pratt 算法)是一个著名的字符串匹配算法,效率很高,但是确实有点复杂。
2731

@@ -33,7 +37,11 @@ KMP 算法(Knuth-Morris-Pratt 算法)是一个著名的字符串匹配算法
3337

3438
读者见过的 KMP 算法应该是,一波诡异的操作处理 `pat` 后形成一个一维的数组 `next`,然后根据这个数组经过又一波复杂操作去匹配 `txt`。时间复杂度 O(N),空间复杂度 O(M)。其实它这个 `next` 数组就相当于 `dp` 数组,其中元素的含义跟 `pat` 的前缀和后缀有关,判定规则比较复杂,不好理解。**本文则用一个二维的 `dp` 数组(但空间复杂度还是 O(M)),重新定义其中元素的含义,使得代码长度大大减少,可解释性大大提高**
3539

36-
> note:本文的代码参考《算法4》,原代码使用的数组名称是 `dfa`(确定有限状态机),因为我们的公众号之前有一系列动态规划的文章,就不说这么高大上的名词了,我对书中代码进行了一点修改,并沿用 `dp` 数组的名称。
40+
::: note
41+
42+
本文的代码参考《算法4》,原代码使用的数组名称是 `dfa`(确定有限状态机),因为我们的公众号之前有一系列动态规划的文章,就不说这么高大上的名词了,我对书中代码进行了一点修改,并沿用 `dp` 数组的名称。
43+
44+
:::
3745

3846
### 一、KMP 算法概述
3947

@@ -65,17 +73,17 @@ int search(String pat, String txt) {
6573

6674
比如 `txt = "aaacaaab", pat = "aaab"`
6775

68-
![](https://labuladong.github.io/pictures/kmp/1.gif)
76+
![](https://labuladong.online/algo/images/kmp/1.gif)
6977

7078
很明显,`pat` 中根本没有字符 c,根本没必要回退指针 `i`,暴力解法明显多做了很多不必要的操作。
7179

7280
KMP 算法的不同之处在于,它会花费空间来记录一些信息,在上述情况中就会显得很聪明:
7381

74-
![](https://labuladong.github.io/pictures/kmp/2.gif)
82+
![](https://labuladong.online/algo/images/kmp/2.gif)
7583

7684
再比如类似的 `txt = "aaaaaaab", pat = "aaab"`,暴力解法还会和上面那个例子一样蠢蠢地回退指针 `i`,而 KMP 算法又会耍聪明:
7785

78-
![](https://labuladong.github.io/pictures/kmp/3.gif)
86+
![](https://labuladong.online/algo/images/kmp/3.gif)
7987

8088
因为 KMP 算法知道字符 b 之前的字符 a 都是匹配的,所以每次只需要比较字符 b 是否被匹配就行了。
8189

@@ -98,21 +106,25 @@ pat = "aaab"
98106

99107
只不过对于 `txt1` 的下面这个即将出现的未匹配情况:
100108

101-
![](https://labuladong.github.io/pictures/kmp/txt1.jpg)
109+
![](https://labuladong.online/algo/images/kmp/txt1.jpg)
102110

103111
`dp` 数组指示 `pat` 这样移动:
104112

105-
![](https://labuladong.github.io/pictures/kmp/txt2.jpg)
113+
![](https://labuladong.online/algo/images/kmp/txt2.jpg)
114+
115+
::: note
116+
117+
这个`j` 不要理解为索引,它的含义更准确地说应该是**状态**(state),所以它会出现这个奇怪的位置,后文会详述。
106118

107-
> note:这个`j` 不要理解为索引,它的含义更准确地说应该是**状态**(state),所以它会出现这个奇怪的位置,后文会详述。
119+
:::
108120

109121
而对于 `txt2` 的下面这个即将出现的未匹配情况:
110122

111-
![](https://labuladong.github.io/pictures/kmp/txt3.jpg)
123+
![](https://labuladong.online/algo/images/kmp/txt3.jpg)
112124

113125
`dp` 数组指示 `pat` 这样移动:
114126

115-
![](https://labuladong.github.io/pictures/kmp/txt4.jpg)
127+
![](https://labuladong.online/algo/images/kmp/txt4.jpg)
116128

117129
明白了 `dp` 数组只和 `pat` 有关,那么我们这样设计 KMP 算法就会比较漂亮:
118130

@@ -147,45 +159,45 @@ int pos2 = kmp.search("aaaaaaab"); //4
147159

148160
为什么说 KMP 算法和状态机有关呢?是这样的,我们可以认为 `pat` 的匹配就是状态的转移。比如当 pat = "ABABC":
149161

150-
![](https://labuladong.github.io/pictures/kmp/state.jpg)
162+
![](https://labuladong.online/algo/images/kmp/state.jpg)
151163

152164
如上图,圆圈内的数字就是状态,状态 0 是起始状态,状态 5(`pat.length`)是终止状态。开始匹配时 `pat` 处于起始状态,一旦转移到终止状态,就说明在 `txt` 中找到了 `pat`。比如说当前处于状态 2,就说明字符 "AB" 被匹配:
153165

154-
![](https://labuladong.github.io/pictures/kmp/state2.jpg)
166+
![](https://labuladong.online/algo/images/kmp/state2.jpg)
155167

156168
另外,处于不同状态时,`pat` 状态转移的行为也不同。比如说假设现在匹配到了状态 4,如果遇到字符 A 就应该转移到状态 3,遇到字符 C 就应该转移到状态 5,如果遇到字符 B 就应该转移到状态 0:
157169

158-
![](https://labuladong.github.io/pictures/kmp/state4.jpg)
170+
![](https://labuladong.online/algo/images/kmp/state4.jpg)
159171

160172
具体什么意思呢,我们来一个个举例看看。用变量 `j` 表示指向当前状态的指针,当前 `pat` 匹配到了状态 4:
161173

162-
![](https://labuladong.github.io/pictures/kmp/exp1.jpg)
174+
![](https://labuladong.online/algo/images/kmp/exp1.jpg)
163175

164176
如果遇到了字符 "A",根据箭头指示,转移到状态 3 是最聪明的:
165177

166-
![](https://labuladong.github.io/pictures/kmp/exp3.jpg)
178+
![](https://labuladong.online/algo/images/kmp/exp3.jpg)
167179

168180
如果遇到了字符 "B",根据箭头指示,只能转移到状态 0(一夜回到解放前):
169181

170-
![](https://labuladong.github.io/pictures/kmp/exp5.jpg)
182+
![](https://labuladong.online/algo/images/kmp/exp5.jpg)
171183

172184
如果遇到了字符 "C",根据箭头指示,应该转移到终止状态 5,这也就意味着匹配完成:
173185

174-
![](https://labuladong.github.io/pictures/kmp/exp7.jpg)
186+
![](https://labuladong.online/algo/images/kmp/exp7.jpg)
175187

176188
当然了,还可能遇到其他字符,比如 Z,但是显然应该转移到起始状态 0,因为 `pat` 中根本都没有字符 Z:
177189

178-
![](https://labuladong.github.io/pictures/kmp/z.jpg)
190+
![](https://labuladong.online/algo/images/kmp/z.jpg)
179191

180192
这里为了清晰起见,我们画状态图时就把其他字符转移到状态 0 的箭头省略,只画 `pat` 中出现的字符的状态转移:
181193

182-
![](https://labuladong.github.io/pictures/kmp/allstate.jpg)
194+
![](https://labuladong.online/algo/images/kmp/allstate.jpg)
183195

184196
KMP 算法最关键的步骤就是构造这个状态转移图。**要确定状态转移的行为,得明确两个变量,一个是当前的匹配状态,另一个是遇到的字符**;确定了这两个变量后,就可以知道这个情况下应该转移到哪个状态。
185197

186198
下面看一下 KMP 算法根据这幅状态转移图匹配字符串 `txt` 的过程:
187199

188-
![](https://labuladong.github.io/pictures/kmp/kmp.gif)
200+
![](https://labuladong.online/algo/images/kmp/kmp.gif)
189201

190202
**请记住这个 GIF 的匹配过程,这就是 KMP 算法的核心逻辑**
191203

@@ -241,29 +253,29 @@ for 0 <= j < M: # 状态
241253

242254
这个 next 状态应该怎么求呢?显然,**如果遇到的字符 `c``pat[j]` 匹配的话**,状态就应该向前推进一个,也就是说 `next = j + 1`,我们不妨称这种情况为**状态推进**
243255

244-
![](https://labuladong.github.io/pictures/kmp/forward.jpg)
256+
![](https://labuladong.online/algo/images/kmp/forward.jpg)
245257

246258
**如果字符 `c``pat[j]` 不匹配的话**,状态就要回退(或者原地不动),我们不妨称这种情况为**状态重启**
247259

248-
![](https://labuladong.github.io/pictures/kmp/back.jpg)
260+
![](https://labuladong.online/algo/images/kmp/back.jpg)
249261

250262
那么,如何得知在哪个状态重启呢?解答这个问题之前,我们再定义一个名字:**影子状态**(我编的名字),用变量 `X` 表示。**所谓影子状态,就是和当前状态具有相同的前缀**。比如下面这种情况:
251263

252-
![](https://labuladong.github.io/pictures/kmp/shadow.jpg)
264+
![](https://labuladong.online/algo/images/kmp/shadow.jpg)
253265

254266
当前状态 `j = 4`,其影子状态为 `X = 2`,它们都有相同的前缀 "AB"。因为状态 `X` 和状态 `j` 存在相同的前缀,所以当状态 `j` 准备进行状态重启的时候(遇到的字符 `c``pat[j]` 不匹配),可以通过 `X` 的状态转移图来获得**最近的重启位置**
255267

256268
比如说刚才的情况,如果状态 `j` 遇到一个字符 "A",应该转移到哪里呢?首先只有遇到 "C" 才能推进状态,遇到 "A" 显然只能进行状态重启。**状态 `j` 会把这个字符委托给状态 `X` 处理,也就是 `dp[j]['A'] = dp[X]['A']`**
257269

258-
![](https://labuladong.github.io/pictures/kmp/shadow1.jpg)
270+
![](https://labuladong.online/algo/images/kmp/shadow1.jpg)
259271

260272
为什么这样可以呢?因为:既然 `j` 这边已经确定字符 "A" 无法推进状态,**只能回退**,而且 KMP 就是要**尽可能少的回退**,以免多余的计算。那么 `j` 就可以去问问和自己具有相同前缀的 `X`,如果 `X` 遇见 "A" 可以进行「状态推进」,那就转移过去,因为这样回退最少。
261273

262-
![](https://labuladong.github.io/pictures/kmp/A.gif)
274+
![](https://labuladong.online/algo/images/kmp/A.gif)
263275

264276
当然,如果遇到的字符是 "B",状态 `X` 也不能进行「状态推进」,只能回退,`j` 只要跟着 `X` 指引的方向回退就行了:
265277

266-
![](https://labuladong.github.io/pictures/kmp/shadow2.jpg)
278+
![](https://labuladong.online/algo/images/kmp/shadow2.jpg)
267279

268280
你也许会问,这个 `X` 怎么知道遇到字符 "B" 要回退到状态 0 呢?因为 `X` 永远跟在 `j` 的身后,状态 `X` 如何转移,在之前就已经算出来了。动态规划算法不就是利用过去的结果解决现在的问题吗?
269281

@@ -358,7 +370,7 @@ for (int i = 0; i < N; i++) {
358370

359371
下面来看一下状态转移图的完整构造过程,你就能理解状态 `X` 作用之精妙了:
360372

361-
![](https://labuladong.github.io/pictures/kmp/dfa.gif)
373+
![](https://labuladong.online/algo/images/kmp/dfa.gif)
362374

363375
至此,KMP 算法的核心终于写完啦啦啦啦!看下 KMP 算法的完整代码吧:
364376

@@ -432,8 +444,8 @@ KMP 算法也就是动态规划那点事,我们的公众号文章目录有一
432444
<details class="hint-container details">
433445
<summary><strong>引用本文的文章</strong></summary>
434446

435-
- [我的刷题心得](https://labuladong.github.io/article/fname.html?fname=算法心得)
436-
- [滑动窗口算法延伸:Rabin Karp 字符匹配算法](https://labuladong.github.io/article/fname.html?fname=rabinkarp)
447+
- [我的刷题心得:算法的本质](https://labuladong.online/algo/essential-technique/algorithm-summary/)
448+
- [滑动窗口算法延伸:Rabin Karp 字符匹配算法](https://labuladong.online/algo/practice-in-action/rabinkarp/)
437449

438450
</details><hr>
439451

@@ -443,9 +455,9 @@ KMP 算法也就是动态规划那点事,我们的公众号文章目录有一
443455

444456
**_____________**
445457

446-
**《labuladong 的算法小抄》已经出版,关注公众号查看详情;后台回复「**全家桶**」可下载配套 PDF 和刷题全家桶**
458+
**《labuladong 的算法笔记》已经出版,关注公众号查看详情;后台回复「**全家桶**」可下载配套 PDF 和刷题全家桶**
447459

448-
![](https://labuladong.github.io/pictures/souyisou2.png)
460+
![](https://labuladong.online/algo/images/souyisou2.png)
449461

450462
======其他语言代码======
451463

0 commit comments

Comments
 (0)