写一下个人对KMP算法的看法
1、我们在做字符串匹配问题时,用暴力方法来进行遍历是O(m*n)的,也就是每次不匹配时,模式串都回到开头,并往右移一位,模式串再次从开头进行匹配,而KMP算法解决的问题就是——不匹配时,是否能将模式串往后多移几位,并且模式串无需再从开头进行匹配,这样达到O(m+n)复杂度,也就是两个字符串都只遍历1次(模式串为常数次)
2、如何做到?假设模式串和符号串当前对比的字符不匹配,那它俩之前的字符肯定是匹配成功了的。那我考查模式串这之前字符的最长相等前后缀(这个自行搜索一下,概念很简单——例如abcab,前两个字符ab和后两个字符ab相等且长度最长了,那么这个字符串abcab的最长相等前后缀长度=2),那我此时去移动模式串,是不是就可以把模式串的前缀,和原文串里面的模式串后缀给匹配上,那这部分就不用我再次进行匹配了,我直接考察匹配完了的下一个字符,于是我们定义了next数组
3、那这个next数组是什么呢,就是计算模式串每一位截止的字串最长前后缀长度,放在对应的位置(由概念可得next[0] = 0)
4、每次模式串p[j]和原文串s[i]不匹配了,模式串就回退到next[j-1]的位置(可以看作将模式串大幅右移且前后缀匹配,直接考察了下一个字符)
5、求next数组的思想:累计+KMP思想
这是我觉得KMP算法一个很奇妙的点,它在进行KMP算法前置工作时,已经用到了 KMP的思想,具体我写在代码注释里面了
其中j指向模式串最后一位
i指向原文串最后一位
版本1
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
def get_next(s):
next = [0] * len(s)
next[0] = 0 # 第一个为0
#j指向模式串的最后一个字母s[j]
#i指向next数组填充位置,也就是原文串的末尾字母为s[i]
#我们不停地将模式串的最后一个字母,也就是s[j]去和s[i]做匹配
j = 0
for i in range(1, len(s)):
#成功匹配,做累计,j+=1 即代表模式串增长了
if s[j] == s[i]:
next[i] = j + 1
j += 1
#匹配失败,即模式串最后一位 != 原文串最后一位,
#根据kmp思想,借助前后缀相等来回退
else:
#回退模式串
#此时next在i之前的部分已经填充了
while j > 0 and s[j] != s[i]:
j = next[j - 1]
#已经退到模式串首位了,并且还是不匹配,这时候代表没有相等前后缀
if j == 0 and s[j] != s[i]:
next[i] = 0
#否则,都算是匹配上了,和最上面if情况一样做累计
else:
next[i] = j + 1
j += 1
return next
next = get_next(needle)
i = 0
j = 0
while i < len(haystack):
if haystack[i] == needle[j]:
j += 1
i += 1
#不匹配,开始回退needle
else:
while j > 0 and haystack[i] != needle[j]:
j = next[j-1]
#如果匹配了,一起往后走
if haystack[i] == needle[j]:
i += 1
j += 1
#否则只有haystack往后走
else:
i += 1
#needle匹配完了
if j == len(needle):
return i - len(needle)
return -1
版本2
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
def get_next(s):
next = [0] * len(s)
next[0] = 0 # 第一个为0
#j指向模式串的最后一个字母s[j]
#i指向next数组填充位置,也就是原文串的末尾字母为s[i]
#我们不停地将模式串的最后一个字母,也就是s[j]去和s[i]做匹配
j = 0
for i in range(1, len(s)):
#成功匹配,做累计,j+=1 即代表模式串增长了
if s[j] == s[i]:
next[i] = j + 1
j += 1
#匹配失败,即模式串最后一位 != 原文串最后一位,
#根据kmp思想,借助前后缀相等来回退
else:
#回退模式串
#此时next在i之前的部分已经填充了
while j > 0 and s[j] != s[i]:
j = next[j - 1]
#已经退到模式串首位了,并且还是不匹配,这时候代表没有相等前后缀
if j == 0 and s[j] != s[i]:
next[i] = 0
#否则,都算是匹配上了,和最上面if情况一样做累计
else:
next[i] = j + 1
j += 1
return next
next = get_next(needle)
i = 0
j = 0
while i < len(haystack):
if haystack[i] == needle[j]:
j += 1
i += 1
#不匹配,开始回退needle
else:
while j > 0 and haystack[i] != needle[j]:
j = next[j-1]
#回退到0了,还是不匹配,那只能haystack往后走了
if haystack[i] != needle[j]:
i += 1
#needle匹配完了
if j == len(needle):
return i - len(needle)
return -1
版本3
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
def get_next(s):
next = [0] * len(s)
next[0] = 0 # 第一个为0
#j指向模式串的最后一个字母s[j]
#i指向next数组填充位置,也就是原文串的末尾字母为s[i]
#我们不停地将模式串的最后一个字母,也就是s[j]去和s[i]做匹配
j = 0
for i in range(1, len(s)):
#成功匹配,做累计,j+=1 即代表模式串增长了
if s[j] == s[i]:
next[i] = j + 1
j += 1
#匹配失败,即模式串最后一位 != 原文串最后一位,
#根据kmp思想,借助前后缀相等来回退
else:
#回退模式串
#此时next在i之前的部分已经填充了
while j > 0 and s[j] != s[i]:
j = next[j - 1]
#已经退到模式串首位了,并且还是不匹配,这时候代表没有相等前后缀
if j == 0 and s[j] != s[i]:
next[i] = 0
#否则,都算是匹配上了,和最上面if情况一样做累计
else:
next[i] = j + 1
j += 1
return next
next = get_next(needle)
j = 0
for i in range(0, len(haystack)):
if haystack[i] == needle[j]:
j += 1
else:
while j > 0 and haystack[i] != needle[j]:
j = next[j - 1]
if haystack[i] == needle[j]:
j += 1
if j == len(needle):
return i - len(needle) + 1
return -1