[最大最长]最长重复子串
给你一个字符串 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 里取个最长的,就找到了答案!