解决的问题
kmp算法是用于解决字符串匹配问题的算法。具体问题是:现有一个长度为n的字符串s和一个长度为m的字符串p,现在要求给出p在s中第一次出现的下标。
可以看看leetcode的第28道题
传统的暴力方法就是两重循环来求解那么时间复杂度就是O(n*m),但是使用kmp算法的时间复杂度是O(n+m)
具体的原理
传统的暴力匹配规则在匹配失败的时候会将p串完全回退到最开始的地方,如下图
也就是说在暴力方法里面,匹配失败之后i会回退到开始匹配的下一个字符的位置,j会直接回退到0
但如果是kmp会怎么处理?
可以看到i并没有回退,而j则是回退到下标1。这是因为当p串中的D不匹配时,会有一个隐藏的信息,即D字符前面的ABA是匹配的,而对于ABA这个字符串来说,它的前缀和后缀中相等的最大串是A,所以j只用移动到这个前缀串的下一位即可。
那么我们只用求得p串中,每一位的移动位置就能解决p串和s串失配时j该移动的位置的问题。所以现在问题就变成了如何求解p串中每一位的移动位置。
首先定义next[x]就是在p[x]失配时,j应该移动到的位置。考虑采用递推的方式去求解,即如果目前已知了next[0]、next[1]…next[x-1],如何去求next[x]。为了描述方便,将next[x-1]记做now
- 第一种情况,如果p[x]==p[now],那么显然可以让next[x]=now+1
- 第二种情况,如果p[x]!=p[now],这时我们需要减少now,具体要减少到多少呢?
我们可以看到上图中的子串A和子串B是相同的,所以应该将now改为next[now-1],这里的意思就是在子串A上继续进行自匹配的过程。
具体的求解next数组的代码即可写作:
def getNext(needle):
nxt = [0]
x = 1
now = 0
while x < len(needle):
if needle[x] == needle[now]:
nxt.append(now + 1)
x += 1
now += 1
elif now:
now = nxt[now - 1]
else:
nxt.append(0)
x += 1
return nxt
那么对于leetcode的第28道题来说完整的题解就应该是:
class Solution:
def getNext(self, needle):
nxt = [0]
x = 1
now = 0
while x < len(needle):
if needle[x] == needle[now]:
nxt.append(now + 1)
x += 1
now += 1
elif now:
now = nxt[now - 1]
else:
nxt.append(0)
x += 1
return nxt
def strStr(self, haystack: str, needle: str) -> int:
if not needle:
return 0
nxt = self.getNext(needle)
i = 0
j = 0
while i < len(haystack) and j < len(needle):
if haystack[i] == needle[j]:
i += 1
j += 1
elif j:
j = nxt[j - 1]
else:
i += 1
if j == len(needle):
return i - j
else:
return -1