Day9代码随想录字符串part02-28. 实现 strStr()、459.重复的子字符串

Day09-KMP

1、作用:KMP主要应用在字符串匹配上。KMP的主要思想是当出现字符串不匹配时,可以知道一部分之前已经匹配的文本内容,可以利用这些信息避免从头再去做匹配了。

2、举例说明: 文本串aabaabaaf 模式串aabaaf

前缀表(所谓的next数组):前缀表是用来回退的,它记录了模式串与主串(文本串)不匹配的时候,模式串应该从哪里开始重新匹配。一开始匹配到aabaa是一致的,但后一位f开始不一致了,于是指针指向aabaa前缀aa的后一位b。在某个字符失配时,前缀表会告诉你下一步匹配中,模式串应该跳到哪个位置记录下标i之前(包括i)的字符串中,有多大长度的相同前缀后缀。(最长公共前后缀common)

**什么是前缀:**所有包含首字母但不含尾字母的所有子串,都称为前缀:a, aa, aab, aaba, aabaa都是前缀

**什么是后缀:**所有包含尾字母但不含首字母的所有子串,都称为后缀:f, af, aaf, baaf, abaaf都是后缀

最长公共前后缀:例如,字符串a的最长相等前后缀为0。 字符串aa的最长相等前后缀为1。 字符串aaa的最长相等前后缀为2。例如:字符串aabaa的最长相等的前缀 和 后缀字符串是 子字符串aa

3、如何计算最长公共前后缀:以aabaaf为例

子字符串前缀后缀最长公共前后缀长度最长公共前后缀
aNoneNone0前后缀都为0,长度为0;
aaaa1前后缀都为a,长度为1;
aabaa/aab/b0前缀aa,后缀ab,不相同,再取前缀a,后缀b,不相同
aabaaab/aa/aaba/ba/a1前缀aab,后缀aba,不相同,再取前缀aa,后缀ba,不相同,再取前缀a,后缀a,相同,长度为1;
aabaaaaba/aab/aa/aabaa/baa/aa/a2前缀aaba,后缀abaa,不相同,再取前缀aab,后缀baa,不相同,再取前缀aa,后缀aa,相同,长度为2;
aabaafaabaa/aaba/aab/aa/aabaaf/baaf/aaf/af/f0前缀aabaa,后缀abaaf,不相同,再取前缀aaba,后缀baaf,不相同,再取前缀aab,后缀aaf,不相同,再取前缀aa,后缀af,不相同,再取前缀a,后缀f,不相同,长度为0.

4、具体代码(伪代码):

构造next数组:next数组指针以及要匹配的字符串

下面要进行三个操作:

  1. **初始化:**定义两个指针i和j,j指向前缀末尾位置,i指向后缀末尾位置:其实j也代表着最长相等前后缀的长度
void getNext(int* next, const string& s)
{
	int j=0;
	next[0] =0;
}
  1. 处理前后缀不相同的情况:

i的初始化是循环遍历的过程,i是1开始才有意义,要不字串长度为1是没有公共子串的

不相等的时候,j向前回退!回退到next[j-1]的值对应的下标!!遇见冲突看前一位(这是这种不后移的代码实现)

回退可能不止一步,所以不是if是while

for (i=1;i<s.size;i++)
{
	while(j>0 && s[i] !=s[j])
	{
		j = next[j-1];
	}
}
  1. 处理前后缀相同的情况

j同时代表最长相等前后串的长度,所以j++;更新next数组

if(s[i] ==s[j])
{
	j++;
	nect[j] = j;
}
(i++)在for循环中完成

使用next数组来做匹配

在文本串s里 找是否出现过模式串t。定义两个下标j 指向模式串起始位置,i指向文本串起始位置。初始化j = 0

  • i就从0开始,遍历文本串:
  • s[i] 与 t[j] 开始比较,如果不同,j就要从next数组找下一个匹配的位置next[j-1] && j>0
  • 如果相同就同时往后移动
  • 如何判断在文本串s里出现了模式串t:j指向了模式串t的末尾,那么就说明模式串t完全匹配文本串s里的某个子串了

28. 实现 strStr()

本题要在文本串字符串中找出模式串出现的第一个位置 (从0开始),所以返回当前在文本串匹配模式串的位置i 减去 模式串的长度,就是文本串字符串中出现模式串的第一个位置。

伪代码:

int j = -1; // 因为next数组里记录的起始位置为-1
for (int i = 0; i < s.size(); i++) { // 注意i就从0开始
    while(j >= 0 && s[i] != t[j + 1]) { // 不匹配
        j = next[j]; // j 寻找之前匹配的位置
    }
    if (s[i] == t[j + 1]) { // 匹配,j和i同时向后移动
        j++; // i的增加在for循环里
    }
    if (j == (t.size() - 1) ) { // 文本串s里出现了模式串t
        return (i - t.size() + 1);
    }
}

实际代码python:

class Solution:
    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
            
    def strStr(self, haystack: str, needle: str) -> int:
        if len(needle) == 0:
            return -1
        next = [0]*len(needle)
        self.getnext(next, needle)
        ne = 0
        for ha in range(len(haystack)):
            while ne>0 and haystack[ha] != needle[ne]:
                ne = next[ne-1]
            if haystack[ha] == needle[ne]:
                ne +=1
            if ne == len(needle):
                return ha - len(needle) +1
        return -1

还有非常简单的直接调用库函数find:

class Solution:
    def strStr(self, haystack: str, needle: str) -> int:
        return haystack.find(needle)

调用index:

class Solution:
    def strStr(self, haystack: str, needle: str) -> int:
        try:
            return haystack.index(needle)
        except ValueError:
            return -1

当然还有暴力方法:

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 

459.重复的子字符串

**题目:**给定一个非空的字符串,判断它是否可以由它的一个子串重复多次构成。给定的字符串只含有小写英文字母,并且长度不超过10000。

示例 1:

  • 输入: "abab"
  • 输出: True
  • 解释: 可由子字符串 "ab" 重复两次构成。

思路:暴力解法时间复杂度O(nn),使用移动匹配O(mn),用kmp是O(m+n)

(1)移动匹配:原理是字符串前半部分和后半部分相同;如果s+s=ss中还能搜索到s(把首位删掉搜索find(s),也就是刚刚28这道题的功能)

(2)KMP:

在由重复子串组成的字符串中,最长相等前后缀不包含的子串是最小重复子串,这里拿字符串s:abababab 来举例,ab就是最小重复单位。

证明:假如前缀是S,后缀是F,前后缀相同+位置相同推导;S[0][1] = F[2][3] =S[2][3] =…

next数组的最后一个地方就是最长相等前后缀也就是next[len(s)-1],所以如果字符串长度len(s) - next[len(s)-1]得到的最小不重复子串能被总长度len(s)整除,就能证明是最小重复单元

class Solution:
    def repeatedSubstringPattern(self, s: str) -> bool:
        if len(s) == 0:
            return False
        next = [0] *len(s)
        self.get_next(next, s)
        if next[-1]!= 0 and len(s)%(len(s) - next[-1])==0:
            return True
        return False

    def get_next(self, next, s):
        j = 0
        next[0] =0
        for i in range(1, len(s)):
            while j>0 and s[j] != s[i]:
                j = next[j-1]
            if s[j] == s[i]:
                j+=1
            next[i] = j

使用find的方法:

class Solution:
    def repeatedSubstringPattern(self, s: str) -> bool:
        n = len(s)
        if n <= 1:
            return False
        ss = s[1:] + s[:-1] 
        print(ss.find(s))              
        return ss.find(s) != -1

  • 6
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值