字符串-kmp

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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值