LeetCode题目
简介
- 要在文本串 text 中找出第一次出现模式串 pattern 的下标,即文本查找,可以用KMP算法
- 先看一个例子:
text: aabaabaafa
pattern: aabaaf
class Solution:
def strStr(self, text: str, pattern: str) -> int:
"""
双指针法, t_idx=0, p_idx=0
- 当 t_idx<len(text), 且 p_idx<len(pattern)
- 若 text[t_idx] 不等于 pattern[p_idx]
- t_idx-=p_idx, p_idx=0 (从头开始匹配)
- 否则
- p_idx+=1 (从下一位继续匹配)
- t_idx+=1
- 若 p_idx==len(pattern)
- 返回 t_idx-len(pattern)
- 返回 -1
"""
t_idx = 0
p_idx = 0
while t_idx < len(text) and p_idx < len(pattern):
if text[t_idx] != pattern[p_idx]:
t_idx -= p_idx
p_idx = 0
else:
p_idx += 1
t_idx += 1
if len(pattern) == p_idx:
return t_idx - len(pattern)
return -1
- 暴力匹配的最坏时间复杂度是 O(n*m),主要问题是在于每次
text[t_idx] 不等于 pattern[p_idx]
(失配)时,text 和 pattern 都要从头开始匹配 - KMP算法可以在每次失配时,利用前面已经匹配好的部分,不必从头开始
KMP算法
- 观察例子,从头开始,当匹配到 pattern 的最后一位时,
f!=b
,但是 f 前面的 aa 被匹配上,所以下一次可以从 b 开始匹配
text: aabaacaabaafa
pattern: aabaaf
- text 也不需要回退,直接从第 6 位的 c 开始,和 pattern 的第 3 位 b 继续匹配
- 上述操作中,在匹配 pattern 的最后一位时,发生失配,因为前面已经匹配的 aabaa,以 aa 开头,aa 结尾,所以从第 3 位 b 继续匹配,如果还是失配,继续查看前面已经匹配的 aa,以 a 开头,a 结尾,所以从第 2 位 a 继续匹配,如果还是失配,则只能从头开始
- 如何决定当前位失配后,应该从第几位继续匹配呢,其实这是在寻找已匹配部分的“最大相等前后缀”,如果得到 pattern 每一位的最大相等前后缀(即从头开始,到该位前一位的子串的最大相等前后缀),则可以在每次失配后,快速决定从哪一位继续匹配
最大相等前后缀
- 字符串的前缀:不包括最后一位的连续子串,记为 prefix
- 字符串的后缀:不包括第一位的连续子串,记为 suffix
- 将最大相等前后缀保存为一个数组,记为 next
- 对 pattern 的每一位
- a: 显然 next[0]=0
- aa: 以 a 开头,以 a 结尾,next[1]=1
- aab: 以 a 开头,以 b 结尾,不存在相等前后缀,next[2]=0
- aaba: 最大相等前后缀为 a,next[3]=1
- aabaa: 最大相等前后缀为 aa,next[4]=2
- aabaaf: 不存在相等前后缀,next[5]=0
- 接下来可以利用 next 数组和双指针法,再做一次
class Solution:
def strStr(self, text: str, pattern: str) -> int:
"""
KMP 算法
- 先计算前缀表 next
双指针法, t_idx=0, p_idx=0
- 当 t_idx<len(text), 且 p_idx<len(pattern)
- 当 p_idx>0, 且 text[t_idx]!=pattern[p_idx]
- p_idx=next[p_idx-1]
- 若 text[t_idx]==pattern[p_idx]
- p_idx+=1
- t_idx+=1
- 若 p_idx==len(pattern)
- 返回 t_idx-len(pattern)
- 返回 -1
"""
next_ = self.get_next(pattern)
t_idx = 0
p_idx = 0
while t_idx < len(text) and p_idx < len(pattern):
while p_idx > 0 and text[t_idx] != pattern[p_idx]:
p_idx = next_[p_idx - 1]
if text[t_idx] == pattern[p_idx]:
p_idx += 1
t_idx += 1
if len(pattern) == p_idx:
return t_idx - len(pattern)
return -1
def get_next(self, pattern):
"""
类似自己对自己进行 KMP
"""
next_ = [0]
j = next_[0]
for i in range(1, len(pattern)):
while j > 0 and pattern[i] != pattern[j]:
j = next_[j - 1]
if pattern[i] == pattern[j]:
j += 1
next_.append(j)
return next_