KMP
next
next数组就是一个前缀表(prefix table)。
前缀表有什么作用呢?
前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。记录了包含当下字符的最长公共前后缀的长度
匹配过程
要在文本串
:aabaabaafa 中查找是否出现过一个模式串
:aabaaf。
可以看出,文本串中第六个字符b 和 模式串的第六个字符f,不匹配了。如果暴力匹配,会发现不匹配,此时就要从头匹配了。
但如果使用前缀表,就不会从头匹配,而是从上次已经匹配的内容开始匹配
,找到了模式串中第三个字符b继续开始匹配
。
那么什么是前缀表
:记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。
文章中字符串的
前缀
是指不包含最后一个字符的所有以第一个字符开头的连续子串。
后缀
是指不包含第一个字符的所有以最后一个字符结尾的连续子串。
前缀表
要求的就是相同
前后缀的长度。
而最长公共前后缀里面的“公共”,更像是说前缀和后缀公共的长度
。这其实并不是前缀表所需要的
所以字符串a的最长相等前后缀为0。 字符串aa的最长相等前后缀为1。 字符串aaa的最长相等前后缀为2
。 等等…。
next数组的代码实现
j是前缀的末尾(所以j也是公共最长前后缀的长度 因为从下标0开始的 next[i]也是包含i的公共最长前后缀的长度),i是后缀的末尾
初始化
j=0 next[0]=0(0位置回退到0) i从1开始 for(i=1;i<len(s);i++)前后缀不同
s[i]!=s[j] 发生冲突 j回退到j前一位指向的值 因为是个不断回退的过程 因为可能不止一次冲突 即while(j>0&&s[i]!=s[j] ) j=next[j-1]
(ps 为什么需要退回冲突的前一位 因为冲突前的位置是匹配的 所以退回到前一位所指向的值 即是最长公共前缀的后一位)
如下图在f处冲突,所以退回到f的前一位a所指向的下标是2 说明最长前缀是2即aa 所以从下标2即b开始匹配
def getnext(s):
next=[]
#初始化
j=0
next.append(0)
for i in range(1,len(s)):
#前后缀不同 回退
while j>0 and s[i]!=s[j]:
j=next[j-1]
# 前后缀相同 j自增
if s[i]==s[j]:
j+=1
# 更新next
next.append(j)
return next
前后缀相同
s[i]==s[j] j++ i++更新next数组
next[i]=j
28. 实现 strStr()
实现 strStr() 函数。
给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串出现的第一个位置(下标从 0 开始)。如果不存在,则返回 -1 。
说明:
当 needle 是空字符串时,我们应当返回什么值呢?这是一个在面试中很好的问题。
对于本题而言,当 needle 是空字符串时我们应当返回 0 。这与 C 语言的 strstr() 以及 Java 的 indexOf() 定义相符。
示例 1:
输入:haystack = “hello”, needle = “ll” 输出:2
示例 2:输入:haystack = “aaaaa”, needle = “bba” 输出:-1
示例 3:输入:haystack = “”, needle = “” 输出:0
class Solution:
def strStr(self, haystack: str, needle: str) -> int:
def getnext(s):
next=[]
#初始化
j=0
next.append(0)
for i in range(1,len(s)):
#前后缀不同 回退
while j>0 and s[i]!=s[j]:
j=next[j-1]
# 前后缀相同 j自增
if s[i]==s[j]:
j+=1
# 更新next
next.append(j)
return next
if needle=='' :
return 0
next=getnext(needle)
j=0
for i in range(len(haystack)):
# 字符不匹配 冲突 回退
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
459. 重复的子字符串
给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。
示例 1:
输入: s = “abab” 输出: true 解释: 可由子串 “ab” 重复两次构成。
示例 2:输入: s = “aba” 输出: false
示例 3:输入: s = “abcabcabcabc” 输出: true 解释: 可由子串 “abc” 重复四次构成。 (或子串 “abcabc”
重复两次构成。)
说明 (数组长度-最长相等前后缀的长度) 正好可以被 数组的长度整除,说明有该字符串有重复的子字符串。
数组长度减去最长相同前后缀的长度相当于是第一个周期的长度,也就是一个周期的长度,如果这个周期可以被整除,就说明整个数组就是这个周期的循环。
使用了前缀表统一减一的实现方式
前提:
next[len - 1] !=-1 说明是有重复子串的
next[len - 1] = 7,next[len - 1] + 1 = 8,8就是此时字符串asdfasdfasdf的最长相同前后缀的长度。
(len - (next[len - 1] + 1)) 也就是: 12(字符串的长度) - 8(最长公共前后缀的长度) = 4, 4正好可以被 12(字符串的长度) 整除,所以说明有重复的子字符串(asdf)。
class Solution:
def repeatedSubstringPattern(self, s: str) -> bool:
def getnext(s):
next=[]
#初始化
j=0
next.append(0)
for i in range(1,len(s)):
#前后缀不同 回退
while j>0 and s[i]!=s[j]:
j=next[j-1]
# 前后缀相同 j自增
if s[i]==s[j]:
j+=1
# 更新next
next.append(j)
return next
next=getnext(s)
rest=len(s)-next[len(s)-1]
if len(s)%rest==0 and next[len(s)-1]!=0:
return True
return False