最长重复子串(后缀数组解法)
字数 727 2025-11-16 05:51:08

最长重复子串(后缀数组解法)

题目描述
给定一个字符串s,找到其中最长的重复子串。重复子串是指在字符串中至少出现两次的连续子串(两次出现的位置可以重叠,但通常认为不重叠更有意义,这里我们考虑可以重叠的情况)。

解题过程

让我用循序渐进的方式为你讲解这个问题的后缀数组解法:

1. 问题分析

  • 我们需要找到在字符串中出现至少两次的最长子串
  • 暴力解法是枚举所有子串,检查每个子串出现的次数,时间复杂度O(n³)
  • 使用后缀数组可以将时间复杂度优化到O(n log n)

2. 后缀数组基本概念

后缀数组包含字符串的所有后缀的起始位置,按字典序排序:

字符串s = "banana"
所有后缀:
0: "banana"
1: "anana" 
2: "nana"
3: "ana"
4: "na"
5: "a"

排序后的后缀数组:
5: "a"
3: "ana"
1: "anana"
0: "banana"
4: "na"
2: "nana"

3. 关键观察

两个后缀的最长公共前缀(LCP)就是它们对应的重复子串。在排序后的后缀数组中,相邻后缀的LCP包含了我们需要的重复子串信息。

4. 构建后缀数组

使用倍增算法构建后缀数组:

def build_suffix_array(s):
    n = len(s)
    # 初始排名(单个字符)
    suffix = list(range(n))
    rank = [ord(ch) for ch in s]
    
    k = 1
    while k < n:
        # 按第一关键字和第二关键字排序
        suffix.sort(key=lambda i: (rank[i], rank[i + k] if i + k < n else -1))
        
        # 重新计算排名
        new_rank = [0] * n
        new_rank[suffix[0]] = 0
        for i in range(1, n):
            prev = suffix[i-1]
            curr = suffix[i]
            # 如果两个元素的第一第二关键字都相同,排名相同
            if rank[prev] == rank[curr] and (prev + k < n and curr + k < n and rank[prev + k] == rank[curr + k]):
                new_rank[curr] = new_rank[prev]
            else:
                new_rank[curr] = new_rank[prev] + 1
        rank = new_rank
        k *= 2
    
    return suffix

5. 计算LCP数组

LCP数组存储相邻后缀的最长公共前缀长度:

def build_lcp_array(s, suffix):
    n = len(s)
    lcp = [0] * n
    # 构建逆后缀数组,用于快速查找位置
    rank = [0] * n
    for i in range(n):
        rank[suffix[i]] = i
    
    k = 0
    for i in range(n):
        if rank[i] == n - 1:
            k = 0
            continue
        
        j = suffix[rank[i] + 1]
        while i + k < n and j + k < n and s[i + k] == s[j + k]:
            k += 1
        
        lcp[rank[i]] = k
        if k > 0:
            k -= 1
    
    return lcp

6. 寻找最长重复子串

在LCP数组中找到最大值,对应的就是最长重复子串:

def longest_repeated_substring(s):
    if not s:
        return ""
    
    suffix = build_suffix_array(s)
    lcp = build_lcp_array(s, suffix)
    
    # 找到LCP数组中的最大值
    max_len = 0
    max_index = 0
    for i in range(len(lcp)):
        if lcp[i] > max_len:
            max_len = lcp[i]
            max_index = i
    
    if max_len == 0:
        return ""
    
    # 从后缀数组中获取重复子串
    start_pos = suffix[max_index]
    return s[start_pos:start_pos + max_len]

7. 完整示例

以字符串"banana"为例:

  • 后缀数组:[5, 3, 1, 0, 4, 2](对应后缀:"a", "ana", "anana", "banana", "na", "nana")
  • LCP数组:[0, 1, 3, 0, 0, 2]
  • 最大LCP值是3,对应后缀"ana"和"anana"的最长公共前缀"ana"
  • 最长重复子串:"ana"

8. 时间复杂度分析

  • 构建后缀数组:O(n log n)
  • 构建LCP数组:O(n)
  • 总体时间复杂度:O(n log n)
  • 空间复杂度:O(n)

这种方法相比暴力解法的O(n³)有了显著的性能提升,特别适合处理长字符串。

最长重复子串(后缀数组解法) 题目描述 给定一个字符串s,找到其中最长的重复子串。重复子串是指在字符串中至少出现两次的连续子串(两次出现的位置可以重叠,但通常认为不重叠更有意义,这里我们考虑可以重叠的情况)。 解题过程 让我用循序渐进的方式为你讲解这个问题的后缀数组解法: 1. 问题分析 我们需要找到在字符串中出现至少两次的最长子串 暴力解法是枚举所有子串,检查每个子串出现的次数,时间复杂度O(n³) 使用后缀数组可以将时间复杂度优化到O(n log n) 2. 后缀数组基本概念 后缀数组包含字符串的所有后缀的起始位置,按字典序排序: 3. 关键观察 两个后缀的最长公共前缀(LCP)就是它们对应的重复子串。在排序后的后缀数组中,相邻后缀的LCP包含了我们需要的重复子串信息。 4. 构建后缀数组 使用倍增算法构建后缀数组: 5. 计算LCP数组 LCP数组存储相邻后缀的最长公共前缀长度: 6. 寻找最长重复子串 在LCP数组中找到最大值,对应的就是最长重复子串: 7. 完整示例 以字符串"banana"为例: 后缀数组:[ 5, 3, 1, 0, 4, 2 ](对应后缀:"a", "ana", "anana", "banana", "na", "nana") LCP数组:[ 0, 1, 3, 0, 0, 2 ] 最大LCP值是3,对应后缀"ana"和"anana"的最长公共前缀"ana" 最长重复子串:"ana" 8. 时间复杂度分析 构建后缀数组:O(n log n) 构建LCP数组:O(n) 总体时间复杂度:O(n log n) 空间复杂度:O(n) 这种方法相比暴力解法的O(n³)有了显著的性能提升,特别适合处理长字符串。