Loading

[最大最长]最长重复子串

给你一个字符串 s ,考虑其所有 重复子串 :即 s 的(连续)子串,在 s 中出现 2 次或更多次。这些出现之间可能存在重叠。

返回 任意一个 可能具有最长长度的重复子串。如果 s 不含重复子串,那么答案为 "" 。

示例 1:
输入:s = "banana"
输出:"ana"

示例 2:
输入:s = "abcd"
输出:""

def longest_dup_substring(s: str) -> str:
    # 构建后缀数组
    suffixes = [s[i:] for i in range(len(s))]
    suffixes.sort()

    # 计算最长公共前缀
    def lcp(a: str, b: str) -> int:
        i = 0
        while i < len(a) and i < len(b) and a[i] == b[i]:
            i += 1
        return i

    max_len = 0
    res = ""
    for i in range(len(suffixes) - 1):
        length = lcp(suffixes[i], suffixes[i + 1])
        if length > max_len:
            max_len = length
            res = suffixes[i][:length]

    return res

下面我来详细解释为什么用 后缀数组 + 最长公共前缀(LCP) 的方式可以找到最长重复子串,而且还挺高效。

🧠 核心思想
我们要找的,是一个在字符串中出现两次及以上的连续子串,且这个子串是最长的。

举个例子:
字符串 s = "banana" 的所有子串中,有几个是重复的,比如:

"a":出现了 3 次

"an":出现了 2 次

"ana":出现了 2 次

"n":出现了 2 次

...

我们想找的是这些中最长的那个 → "ana"。

🔧 为什么后缀数组可以帮我们做到这件事?
第一步:构造所有的后缀
把字符串所有的后缀列出来:

css
复制
编辑
banana
anana
nana
ana
na
a
这些是 s[i:] 的所有形式(从每个位置开始的子串)。

第二步:排序这些后缀(字典序)
css
复制
编辑
a
ana
anana
banana
na
nana
排序的目的是让相似的后缀排在一起。例如:

"ana" 和 "anana" 是很相似的,它们都以 "ana" 开头。

"na" 和 "nana" 是很相似的,它们都以 "na" 开头。

如果某个子串在字符串中重复了多次,那么它就会出现在多个后缀中,而这些后缀就会有一个较长的公共前缀。

第三步:比较相邻后缀之间的最长公共前缀(LCP)
现在我们按顺序比较每一对相邻的后缀,找它们之间最长的公共前缀:

比如:

a 和 ana → LCP 是 "a"

ana 和 anana → LCP 是 "ana" ✅ 这是我们要找的!

anana 和 banana → LCP 是 ""

...

我们从中找一个最长的 LCP 就行了!

🧠 结论
所以,为什么这个方法能找出最长的重复子串:

重复子串意味着它在多个后缀中存在。

后缀排序让相似的后缀相邻排列。

最长的重复子串就会出现在某对相邻后缀的最长公共前缀中。

我们只要在所有这些 LCP 里取个最长的,就找到了答案!

posted @ 2025-04-18 14:44  Duancf  阅读(28)  评论(0)    收藏  举报