最长重复子串

最长重复子串

给你一个字符串s,考虑其所有 重复子串 ,即s的(连续)子串,在s中出现2次或更多次。这些出现之间可能存在重叠。
返回 任意一个 可能具有最长长度的重复子串。如果s不含重复子串,那么答案为""
示例1
输入:s = “banana”
输出:“ana”
示例2
输入:s = “abcd”
输出:“”
提示:

  • 2 < = s . l e n g t h < = 3 ∗ 1 0 4 2 <= s.length <= 3 * 10^4 2<=s.length<=3104
  • s由小写英文字母组成

算法思想

该问题求解运用了二分、哈希、滑动窗口的算法思想

二分法

该问题需要求解的是重复子串的最大长度length,其范围在[0,len(s)-1]之间,由于该问题具有明显的分界,处于[0,length]之间均能找到重复子串,[length,len(s)-1]之间均无重复子串,可以考虑使用二分法进行处理(注意二分法的边界问题)。问题求解的基本框架为:

while left <= right:
	mid = (left + right) // 2
	if check(mid): # 此处check仅表示是否存在长度为mid重复子串,不同于正式实现时的check函数
		left = mid + 1
		记录重复子串的起始索引strat和长度length
	else:
		right = mid - 1
	return s[start : start+length]

Rabin-Karp算法(字符串哈希)

Rabin-Karp算法是一种哈希,为一个字符串映射一个哈希值。
首先为每一个字符(小写字母)分配一个数字,将其视为26进制数,在将其转化为十进制表示,将该结果作为字符串的哈希值。判断是否为重复子串时,只需将两个字符串的哈希值进行比较即可(1.由于字符串长度较大,在计算时一般要进行取模操作;2.由于进行了取模操作,不同的字符串有可能在取模后回对应同一个哈希值,造成误判,可以选择较大的质数作为模数或选取多个模数)。

滑动窗口

在计算哈希值时,如果对每一个子串都按照进制转换方法计算,计算量会很大。
对于子串bcdb,对应哈希表示为1231,假设此时寻找的子串长度为3,首先需要按照进制转化方法计算123的哈希值为 h 1 = 1 ∗ 2 6 2 + 2 ∗ 26 + 3 h_1=1*26^2+2*26+3 h1=1262+226+3,而需要计算的下一个子串为231,其哈希值为 h 2 = 2 ∗ 2 6 2 + 3 ∗ 26 + 1 h_2=2*26^2+3*26+1 h2=2262+326+1
在这里插入图片描述
根据滑动窗口原理, h 2 h_2 h2可通过 h 1 h_1 h1减去头部已滑出窗口范围的 1 ∗ 2 6 2 1*26^2 1262后,剩余部分左移乘以进制数,再加上末尾滑进窗口范围的1即可求得。通过滑动窗口可以大幅减少计算哈希值所消耗的时间。

程序代码

class Solution:
    def longestDupSubstring(self, s: str) -> str:
        def check(sublen):
            num1 = num2 = 0
            sl1 = pow(base,sublen-1,mod1)
            sl2 = pow(base,sublen-1,mod2)
            for i in range(sublen):
                num1 = (num1 * base + arr[i]) % mod1
                num2 = (num2 * base + arr[i]) % mod2
            record = set([(num1,num2)])
            for i in range(slen - sublen):
            	# 滑动窗口
                num1 = ((num1-arr[i]*sl1)*base+arr[sublen+i])%mod1
                num2 = ((num2-arr[i]*sl2)*base+arr[sublen+i])%mod2
                if (num1,num2) in record:
                    return i+1 # 返回重复子串的起始索引
                record.add((num1,num2))
            return -1 # 不存在重复子串则返回-1

        base, mod1, mod2 = 26, 1313113, 1313137
        arr = [ord(x) - ord("a") for x in s]
        slen = len(s)
        left, right = 0, slen-1
        length, start = 0, -1
        while left <= right:
            mid = (left + right) // 2
            index = check(mid)
            if index != -1: # 未找到重复子串
                length = mid
                start = index
                left = mid + 1
            else:
                right = mid - 1
        return s[start : start + length]

算法实现过程中遇到的问题

append方法超时

在最初实现时,记录已有哈希的record使用的是List,发现一个不存在的哈希数对时使用append方法将其加入到record列表中。由于append方法是在列表末尾加入元素,每次都要对列表进行遍历,导致时间开销极大,在跑某一组数据时耗时5000+ms,直接超时。
之后将record用set集合表示,发现一个不存在的哈希数对时使用add方法加入set中,上述数据耗时128ms。(大概由于set无序,add方法不用对set进行遍历)

取模导致的非重复子串误判

由于字符串可能相当大,在对哈希结果取模后相同的概率也会较大。
最初只选用一个模数,只能跑过一半的测试样例。尝试增加一个模数,用两个模数取模得到的哈希数对进行判别,能够通过大部分测试样例。最后几个测试样例字符串长度很大,通过多次尝试增大模数最终成功通过全部样例。(可以尝试使用双底数双模数)

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值