主要思想:当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。如何记录已经匹配的内容时kmp算法的重点。
解决问题:利用前缀表解决字符串匹配问题(一个字符串中是否出现另一个字符串)
暴力解法:两层for循环,时间复杂度O(m*n)
前缀:包含首字母不包含尾字母的所有字符串组合
后缀:包含尾字母不包含首字母的所有字符串组合
最长相等前后缀:
例:aabaaf
前缀字符串 | 最长相等前后缀数量(前缀表) | 解释 |
---|---|---|
a | 0 | 没有前后缀 |
aa | 1 | 从前和后数只有a相同 |
aab | 0 | 前后不相同 |
aaba | 1 | 前a和后a相同 |
aabaa | 2 | 前a和后a相同,前aa和后aa相同 |
aabaaf | 0 | 首字母a和尾字母f不同 |
那么其前缀表为next = [0, 1, 0, 1, 2, 0]
假设两个字符串,主串"mississippi", 待匹配串"issip",判断主串中是否包含待匹配串。
1.首先构建待匹配串的前缀表
2.根据前缀表判断主串中是否包含待匹配串
那么步骤一中如何构建字符串的前缀表:
def getnext(str2): # str2要创建前缀表的字符串参数
b = len(str2)
next = [0 for _ in range(b)] #定义好next空数组
j = 0 #前缀初始位置,同时也是相等前后缀的长度
for i in range(1,b,1): #后缀指针位置变化
while j > 0 and str2[j] != str2[i]: #判断前后缀初始位置不相等退回发生分歧的位置
j = next[j-1] #j = 0 时,前缀表只能是next = [0]
if str2[j] == str2[i]:
j += 1
next[i] = j #不仅记录旧的子串的相等前后缀,也记录新的
return next
待匹配串"issip" 的前缀表为next = [0, 0, 0, 1, 0],试验一下:
print(getnext('issip'))
>>> [0, 0, 0, 1, 0]
如何步骤2中根据前缀表判断主串中是否包含待匹配串呢?
思路:按顺序匹配两个字符串当两个字符串出现不匹配字母时,找到待匹配串不匹配字母的前一个字母,找到前缀表中最大的数,并以此为下标在待匹配串中继续与查找串中不匹配点进行匹配。
a = len(str1)
b = len(str2)
j = 0 #待匹配串的指针位置
next = self.getnext(str2)
for i in range(a): #主串的指针位置
while j > 0 and str1[i] != str2[j]: #与创建前缀表思想相似,若产生分歧,则待匹配串
j = next[j-1] #倒退回产生分歧的位置
if str1[i] == str2[j]:
j += 1
整体代码如下:若存在则返回主串中穿线待匹配串位置的第一个下标,若待匹配串为空,则返回0,否则返回-1
class Solution(object):
def strStr(self, haystack, needle):
"""
:type haystack: str
:type needle: str
:rtype: int
"""
a = len(haystack)
b = len(needle)
if b == 0:
return 0
j = 0
next = self.getnext(needle)
print(next)
for i in range(a):
while j > 0 and haystack[i] != needle[j]:
j = next[j - 1]
if haystack[i] == needle[j]:
j += 1
if j == b:
return i - j + 1
return -1
def getnext(self, needle):
b = len(needle)
next = [0 for _ in range(b)]
j = 0
for i in range(1, b, 1):
while j > 0 and needle[j] != needle[i]:
j = next[j - 1]
if needle[j] == needle[i]:
j += 1
next[i] = j
return next
#测试
s = Solution()
print(s.strStr("mississippi","issip"))
#输出
>>> 4
参考链接: