字符串——3.重复的子字符串

力扣题目链接

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

示例:

输入:"abab"
输出:True

题干很简单,也很容易理解,完整思路如《代码随想录》所示,归根结底可以总结出两种解法(暴力法不考虑)。

解法一:

如果一个字符串s是由一个子串重复多次构成,那么s+s拼接的字符串中,必定有一个s(不是拼接的那两个),如图。

那么题目就转化为了在s+s中寻找有没有s,利用python中的find函数就可以完成了,但是在s+s中寻找s总感觉奇奇怪怪的,既然是s+s那其中不本来就有s么,怎么判断是原始的s还是新组成的字符串中的s呢?那就可以将新组成的s+s第一个字符和最后一个字符删除,这样就不存在两端的s了。得到完整代码:

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

先计算字符串s的长度,如果长度小于等于1,那字符串最多只有一个字符,也不用考虑重复不重复的问题了,直接return到False。定义新的字符串ss为除s第一个元素的字符串与除s最后一个字符串的俩字符串拼接。

return到ss.find(s) != -1,find函数会返回s在ss中首次出现的位置索引,所以如果位置所以不等于-1的话则代表ss中存在s,那就return到True,反之则return到Flase,如果不好理解,就直接加个if条件吧。

解法二:

利用find的方法虽然代码简单,但是时间复杂度高。所以出现了解法二,时间复杂度低,但可能不是那么容易理解。

解法二是利用KMP算法进行求解,具体的关于KMP算法可以自行去学习,这里根据完整代码进行分析:

class Solution:
    def repeatedSubstringPattern(self, s: str) -> bool:  
        if len(s) == 0:
            return False
        nxt = [0] * len(s)
        self.getNext(nxt, s)
        if nxt[-1] != -1 and len(s) % (len(s) - (nxt[-1] + 1)) == 0:
            return True
        return False
    
    def getNext(self, nxt, s):
        nxt[0] = -1
        j = -1
        for i in range(1, len(s)):
            while j >= 0 and s[i] != s[j+1]:
                j = nxt[j]
            if s[i] == s[j+1]:
                j += 1
            nxt[i] = j
        return nxt

判断字符串s长度是不是等于0,接着定义了一个新的数组nxt,nxt是长度和s一样的空数组。然后利用一个自定义的函数getNext更新了nxt数组,接着对nxt数组进行了一个判定,就直接得到了最终的结果了,这是整个流程。

首先,我们来分析一下这个自定义函数getNext,在分析之前,我先简单讲一下这个解法的原理,如果字符串s是由它的子串重复构成的,那么一定存在两种特征,一是最小子串的长度是能被原始子串s长度整除的(这个很容易理解,最小子串长度如果是3的话,那你重复构成的s长度肯定是3的倍数吧);二是在s前后删除最小子串后得到的两个字符串应该是一样的,如图:

接着,我们来看自定义函数getNext,首先将数组第一个元素定义为-1,又把-1赋值给j,光看这一步看不出什么意思,那么我们结合后面的代码分析。

以上图的abababab为例,第一次循环i=1,因为j=-1,while不匹配;因为s[1]=b和s[j+1]=s[0]=a不相等,if不匹配。那么将nxt数组位置索引1赋值为-1,当前nxt为:

第二次循环i=2,因为j=-1,while不匹配;因为s[2]=a和s[j+1]=s[0]=a相等,所以进入if,j+1=0。将0赋值给nxt[2],当前nxt为:

第三次循环i=3,因为j=0,s[3]=b,s[j+1]=s[1]=b,两者相等,while不匹配,进入if,j=1,n[3]=1,当前nxt为:

继续循环,直到最后得到nxt为:

结合循环进行分析,

            if s[i] == s[j+1]:
                j += 1
            nxt[i] = j

如果一直循环都没有和第一个字符重复的字符存在,那么nxt[i]就一直为-1。直到存在一个字符和第一个字符相同,j开始变化+1,但光有第一个字符相等也不行,我们还要看后续字符是不是和前面那个子串一样

            while j >= 0 and s[i] != s[j+1]:
                j = nxt[j]

当存在有和第一个字符一样的时候,如果后续开始和前面字符串重复了,那么恭喜你,j可以一直叠加1。那如果从重复值开始后的相对位置的s[i]不等于前面字符串相对位置的字符了呢?那就证明还没找到最小子串,即abac,虽然a重复了,但后续的c和b又不一样了,那么这种情况下就把nxt[j],即对应前面字符串相对位置的数赋值给j,此时肯定是-1。

直到所有循环结束,如果是由子串重复组成的话,那么nxt的最后一个元素值肯定为len(s)减去子串长度再-1。因此进入 if nxt[-1] != -1 and len(s) % (len(s) - (nxt[-1] + 1)) == 0:判断。

首先判断nxt[-1] != -1意味着如果nxt最后一个数为-1,那肯定不是由子串重复构成,如果不为-1,那么可能有两种情况,一是由子串重复构成,二是由子串出现且后续字符和子串有部分相等,例如ababac,虽然不是由重复子串构成,但存在一次重复,则nxt最后一个也不会为0。

接着就需要判断len(s) % (len(s) - (nxt[-1] + 1)) == 0。根据之前讲过的条件,去整除判断是否由子串重复构成。

  • 53
    点赞
  • 29
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值