字符串匹配是一道非常经典的题目,在这里,我想写一些自己的见解,从暴力匹配到RK ,最后使用KMP。
对于主串 S 和模式串 P
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 |
主串 | B | B | C | A | B | C | D | A | B | A | B | C | D | A | B | C | D | A | B | D | E |
模式串 | A | B | C | D | A | B | D | ||||||||||||||
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
BF
首先暴力匹配就很容易理解,在主串中找到模式串的第一个字符后,主串和模式串同时往后移动一位,直到 失配 或者 匹配成功。
代码:
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
j = 0
for i in range(len(haystack) - len(needle) + 1):
while j < len(needle) and haystack[i+j] == needle[j]:
j += 1
if j == len(needle):
return i
j = 0
return -1
复杂度分析:
- 时间复杂度:O(m*n)
- 空间复杂度:O(1)
KMP
说KMP之前,有必要演示一遍 BF 的流程
1)
2)
在进行到 p[6] 的时候才失配,按照暴力匹配的思想,我们需要退回,使用 S[4] 去和模式串进行匹配,无疑一直到 S[7] 之前,都是不会匹配成功的,那这样的话,能不能直接跳到 S[7] 呢?
是可以的,不仅能直接跳到 s[7] ,而且还能跳到 s[9]。
也就是说,当我们失配时,我们希望主串保持不动,只是改变,主串当前位置和模式串中的另一个字符去匹配。
现在,我们的问题就变成,如何去找,该和哪一个字符进行匹配。
观察上面例子,发现,j 由 6 变成了 2,嗯嗯 2 = len('AB'),不管你信不信,这并不是一个巧合,接下来,你也可以多试一试其他的例子。
那就好办了,我们的问题现在变成了,如何找到最长公共前缀后缀,很拗口,说的简单就是
找到一个 P[K:K+N] = P[0:N]。
模式串 | A | B | C | D | A | B | D |
j | 0 | 1 | 2 | 3 | 4 | 5 | 6 |
0 | 0 | 0 | 0 | 1 | 2 | 0 |
我们找到的这个东东称作 next,next的作用是,失配时,帮助我们回溯到一个正确的位置。
下面给出寻找 next 的代码。
实际上是一个 dp 的过程,
分为两种情况
- 匹配成功时 j + 1,赋值给next[i]
- 匹配失败时进行回溯,回溯到上一次匹配成功的地方,j = p_next[j-1]
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
def getNext(p):
p_next = [0] * len(p)
j = 0
for i in range(1,len(p)):
while j > 0 and p[i] != p[j]:
j = p_next[j-1] # 匹配失败,回到上次匹配成功的地方
if p[i] == p[j]:
j += 1
p_next[i] = j
return p_next