代码随想录27期|Python|Day9|字符串总结|双指针总结|KMP初探(28. 实现 strStr()、 459.重复的子字符串)

字符串总结

字符串类类型的题目,往往想法比较简单,但是实现起来并不容易,复杂的字符串题目非常考验对代码的掌控能力。

双指针法是字符串处理的常客。

题目类型的总结可以看代码随想录(很全面)

字符串题目总结:代码随想录 (programmercarl.com)

双指针总结

题目总结可以看代码随想录,注意需要结合题目!不只是看方法论,特别是对于内存和时间要求高的。

代码随想录 (programmercarl.com)

KMP初探(较难,可以回头再看)

代码随想录讲解视频链接:帮你把KMP算法学个通透!(理论篇)_哔哩哔哩_bilibili

KMP算法是通过预处理模式串来构建一个匹配表 (next数组),然后在匹配时使用这个匹配表来快速地判断当前位置的字符是否匹配。它的时间复杂度是O(m+n),其中m是模式串的长度,n是文本串的长度。也就是说,KMP其实可以“保存已经匹配到的字符”,从而不需要每次都从头开始遍历。(图:代码随想录)

代码随想录27期|Python|Day9|字符串总结|双指针总结|KMP初探(28. 实现 strStr()、 459.重复的子字符串)_第1张图片

思路

根据KMP算法,我们大致分为以下几个步骤:

1、求解needle的next数组(或者有的人叫prelix数组);

2、按照next和haysatck匹配,如果完全通过,则ojbk;如果不是,则在needle中回退到现在的next数组的值对应的needle索引位置处,继续寻找。

求解相等前缀表next

那么什么是前缀表:记录下标i之前(包括i)的字符串中,有多大长度的相同前缀和后缀。

前缀是指不包含最后一个字符的所有以第一个字符开头的连续子串

后缀是指不包含第一个字符的所有以最后一个字符结尾的连续子串

为什么用前缀表:因为我们现在只需要判断不匹配处所对应的前缀的最后的位置就可以继续在haysatck继续找了(因为这一部分是相同的,不需要从needle的头部开始)。

代码

暴力法

class Solution(object):
    def strStr(self, haystack, needle):
        """
        :type haystack: str
        :type needle: str
        :rtype: int
        """
        # 暴力法
        m, n = len(haystack), len(needle)
        for i in range(m):
            if haystack[i: i + n] == needle:
              return i
        return -1

但是时间和内存居然很小(exm,r u kidding?)

代码随想录27期|Python|Day9|字符串总结|双指针总结|KMP初探(28. 实现 strStr()、 459.重复的子字符串)_第2张图片

next前缀表法

class Solution(object):
    # 前缀表next法,next储存当前位置包含的相等前后缀子串的数目
    def getnext(self, next, s):
      j = 0  # 找前缀的最后一个位置的指针
      next[0] = 0
      for i in range(1, len(s)):  # i作为遍历指针,从1开始
        while j > 0 and s[j] != s[i]:  # 当前后不相等,让j指向上一个next的位置
          j = next[j - 1]
        if s[i] == s[j]:  # 如果找到了相等的,更新j的个数
          j += 1
        next[i] = j  # 每走一次i,都要记录j的个数

    def strStr(self, haystack, needle):
        """
        :type haystack: str
        :type needle: str
        :rtype: int
        """
        if len(needle) == 0:
          return 0  # 模拟实现C++的strStr函数返回空字符串的匹配结果
        next = [0] * len(needle)
        self.getnext(next, needle)
        j = 0
        # 查找的逻辑和查找前缀差不多
        for i in range(len(haystack)):
          while j > 0 and haystack[i] != needle[j]:
            j = next[j - 1]  # 匹配不一致,j回到上一个位置处next的值对应的needle的索引处
          if haystack[i] == needle[j]:
            j += 1  # 匹配正确,累计j的个数 
          if j == len(needle):  # j可以一直累积到needle的长度说明全部找到
            return i - len(needle) + 1
        return -1

移动匹配+find库函数

基本原理:将两个s相加如果仍出现一个完整的s,那么就是由重复子串组成。(图:代码随想录)代码随想录27期|Python|Day9|字符串总结|双指针总结|KMP初探(28. 实现 strStr()、 459.重复的子字符串)_第3张图片

注意!!拼接之前需要分别去掉s的头部和尾部,不然在s+s里找到原始的s是没用滴~

class Solution(object):
    def repeatedSubstringPattern(self, s):
        """
        :type s: str
        :rtype: bool
        """
        n = len(s)
        if n <= 0:
            return False
        ss = s[1:] + s[:-1]  # 需要弃掉首尾
        return ss.find(s) >= 0

459. 重复的子字符串​​​​​​​

KMP法

这里有一个trick值得借鉴:

根据最大相等前后缀的概念,我们需要找的到前缀第一个值,一定在隔一段距离的后缀第一个值出现,也就意味着对应在原字符串中,开头出现的字符出现在了相隔一段距离之后;以此类推,此时原字符串中的元素对应在前缀串中,再被对应到后缀串中;也就意味着如果这个过程可以持续到字符串结束。

那么前缀和后缀中间差的这一段“没有对齐的字符串”,就会被不断投射到后面,形成了循环。那么我们只要知道,这样一个“没有对齐的字符串”的长度,能不能被原字符串的长度整除即可。

代码随想录27期|Python|Day9|字符串总结|双指针总结|KMP初探(28. 实现 strStr()、 459.重复的子字符串)_第4张图片

class Solution(object):
    def repeatedSubstringPattern(self, s):
        """
        :type s: str
        :rtype: bool
        """
        next = [0]*len(s)
        self.getnext(next, s)
        if len(s) == 0:
            return 0
        # 判断最长的前缀串和总长度的差值是否可以背总长度整除
        if next[-1] != 0 and len(s) % (len(s) - next[-1]) == 0:
            return True
        return False
        # KMP
    def getnext(self, next, s):
        j = 0
        next[0] = 0
        for i in range(1, len(s)):
            # 回退
            while j > 0 and s[i] != s[j]:
                j = next[j - 1]
            # 更新
            if s[i] == s[j]:
                j += 1
            next[i] = j
        return next

 OMG第9天完结

你可能感兴趣的:(数据结构)