python KMP算法查找子字符串
最近在刷leecode,碰见字符串检索题。原题是这样的,有两个字符串,一长一短,就看看短的是不是在长的里面,如果在返回第一个字符的下标。
因为总忘记,所以记录下我的思路是咋捋顺的,其实看这一篇就够了,从头到尾彻底理解KMP
- 最开始也是想,暴力执法,维护两个下标,一个划长字符串,一个来回遍历段字符串,直接上代码(python 里相对简单,功能比对都做了封装,但是原理一样的)。
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
n = len(haystack)
l = 0
r = len(needle)
# 维护一个短字符串长度,在长字符串滑动着比,就相当于把短字符串看作一个字符
if r == 0:
return 0
while r <= n:
# 比较像不像
if haystack[l: r] == needle:
return l
# 不像就下一个,一个一个往后移
l += 1
r += 1
return -1
- 但上面这样的做法,太暴力了,比对不成功的话,每次移动一个小单位,有点浪费,能不能智能地知道往后移动多少位,毕竟字符串都是已经见过的。比如:
0123456789
a = "mississipp"
01234
b = "issip"
a和b两个字符串,按照之前的方式肯定会挨个比下去,模拟下过程
第一次: a[0] b[0] "m" 和 "i" 不匹配 对下一个长字符串下标加一,短字符串下标归零
"mississipp"
"issip"
第二次: a[1] b[0] "i" 和 "i" 匹配,往后走 下标都加一
: a[2] b[1] "s" 和 "s" 匹配,往后走 下标都加一
: a[3] b[2] "s" 和 "s" 匹配,往后走 下标都加一
: a[4] b[3] "i" 和 "i" 匹配,往后走 下标都加一
: a[5] b[4] "s" 和 "p" 不匹配,比对下一个长字符串下标加一,短字符串下标归零
"mississipp"
"issip"
第三次: a[2] b[0] "s" 和 "i" 不匹配,比对下一个长字符串下标加一,短字符串下标归零
......
每次不匹配都是往后延一个,就像第二次的时候,我都已经看到a[1:5]是什么样子的了,还要我再从a[2]开始么?有点不公平奥,思考下,既然都看见了,那也应该知道我往后移动几位最合适了,或者说移动后的几位起码要和子字符串的开头要一样,这样我就对比后面的几位好了。
下面就是如何知道往后移动几位思考,我的想法比较幼稚,就是每移动一位看下匹配的字符梳理,比如在第二次的时候,这么做。
"mississipp"
"issip"
维护一个下标index,这个小标记录着如果下一位不匹配,和子字符串做对比,该移动几位
第二次: a[1] b[0] "i" 和 "i" 匹配,往后走 下标都加一,
"mississipp"
"issip"
假如下一位不匹配 index = 1
: a[2] b[1] "s" 和 "s" 匹配,往后走 下标都加一,
"mississipp"
"issip"
假如下一位不匹配 index = 2
: a[3] b[2] "s" 和 "s" 匹配,往后走 下标都加一,
"mississipp"
"issip"
假如下一位不匹配 index = 3
: a[4] b[3] "i" 和 "i" 匹配,往后走 下标都加一,
"mississipp"
"issip"
假如下一位不匹配 这时又出现了"i"正好和子字符串的第一位开始记下这个位置,index = 4
: a[5] b[4] "s" 和 "p" 不匹配,比对下一个长数组下标加一,短数组下标归零
"mississipp"
"issip"
假如下一位不匹配 这时又出现了"is"正好和子字符串的前两位一样,下标不用变,index = 4
第三次: a[1+(4-1)] b[0] "i" 和 "i" 匹配,往后走 下标都加一,
这样做的麻烦就是每移动一位都要算下下一位不匹配,我该移动几位,计算量更大了,能不能再方便一点,还有既然已经知道移动几位了,那也知道移动几位后与子字符串的前面几位是相同的,就没必要去比对了,直接略过从不同的地方比较。
0123456789
a "mississipp"
01234
b "issip"
我知道移动了3位,从a[1+(4-1)]也就是a[4]开始比,但我还知道index被截留了一次
那么我就应该知道,a[4]a[5]其实和b[0]b[1]是相同的,比对的时候直接比后面的就好了
012345 6789
a "missis sipp"
01 234
b "is sip"
以上是我自己的想法,越想越复杂,还是直接看官方的说法吧也就是最长相等前后缀,会根据子串维护一个最大长度表。
失配时,模式串向右移动的位数为:失配字符所在位置 - 失配字符对应的next 值
代码说话
-10001
"issip"
class Solution:
# 做一个最大长度表
def getNext(self, s, next):
j = -1
next[0] = j
for i in range(1, len(s)):
while j >= 0 and s[i] != s[j+1]:
j = next[j]
if s[i] == s[j+1]:
j += 1
next[i] = j
return
def strStr(self, haystack: str, needle: str) -> int:
if len(needle) == 0:
return 0
j = -1
next = [0] * (len(needle))
self.getNext(needle, next)
for i in range(len(haystack)):
# 不匹配的时候我轻松知道回溯几位
while j >= 0 and haystack[i] != needle[j+1]:
j = next[j]
if haystack[i] == needle[j+1]:
j += 1
if j == len(needle)-1:
return i - len(needle) + 1
return -1
本文来自博客园,作者:赫凯,转载请注明原文链接:https://www.cnblogs.com/heKaiii/p/17137429.html