最长重复子串(后缀数组解法)
字数 926 2025-11-15 19:49:01

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

题目描述
给定一个字符串s,找到最长的重复子串的长度。"重复"意味着在字符串中至少出现两次(可以重叠)的连续子串。要求时间复杂度优于O(n²)。

解题过程

1. 问题分析

  • 暴力解法需要枚举所有子串O(n²)并检查出现次数O(n),总复杂度O(n³)
  • 我们需要利用后缀数组和高度数组来优化

2. 后缀数组基础概念

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

示例:s = "banana"

  • 后缀:
    0: "banana"
    1: "anana"
    2: "nana"
    3: "ana"
    4: "na"
    5: "a"

  • 排序后的后缀数组SA:[5, 3, 1, 0, 4, 2]
    (对应后缀:"a", "ana", "anana", "banana", "na", "nana")

3. 高度数组概念

高度数组LCP[i]表示排序后相邻两个后缀的最长公共前缀长度。

对于SA = [5,3,1,0,4,2]:

  • LCP[1]:比较"a"和"ana" → 公共前缀"a" → 长度1
  • LCP[2]:比较"ana"和"anana" → 公共前缀"ana" → 长度3
  • LCP[3]:比较"anana"和"banana" → 公共前缀"" → 长度0
  • LCP[4]:比较"banana"和"na" → 公共前缀"" → 长度0
  • LCP[5]:比较"na"和"nana" → 公共前缀"na" → 长度2

4. 关键观察

最长重复子串的长度就是高度数组中的最大值!

为什么?

  • 排序后相邻后缀的公共前缀就是重复出现的子串
  • 高度数组记录了这些公共前缀的长度
  • 最大值就是最长的重复子串

5. 构建后缀数组

使用倍增算法,时间复杂度O(n log n):

def build_suffix_array(s):
    n = len(s)
    # 初始排序:按第一个字符排序
    sa = list(range(n))
    rank = [ord(ch) for ch in s]
    
    k = 1
    while k < n:
        # 按(rank[i], rank[i+k])双关键字排序
        sa.sort(key=lambda i: (rank[i], rank[i + k] if i + k < n else -1))
        
        # 重新计算rank
        new_rank = [0] * n
        new_rank[sa[0]] = 0
        for i in range(1, n):
            prev = (rank[sa[i-1]], rank[sa[i-1] + k] if sa[i-1] + k < n else -1)
            curr = (rank[sa[i]], rank[sa[i] + k] if sa[i] + k < n else -1)
            new_rank[sa[i]] = new_rank[sa[i-1]] + (1 if prev != curr else 0)
        
        rank = new_rank
        if rank[sa[n-1]] == n-1:
            break
        k *= 2
    
    return sa

6. 构建高度数组

def build_lcp(s, sa):
    n = len(s)
    lcp = [0] * n
    rank = [0] * n
    
    # 构建rank数组:rank[i]表示后缀i在sa中的位置
    for i in range(n):
        rank[sa[i]] = i
    
    k = 0
    for i in range(n):
        if rank[i] == n-1:
            k = 0
            continue
        
        j = sa[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

7. 完整算法

def longest_repeating_substring(s):
    if not s:
        return 0
    
    sa = build_suffix_array(s)
    lcp = build_lcp(s, sa)
    
    return max(lcp) if lcp else 0

# 示例
s = "banana"
result = longest_repeating_substring(s)  # 返回3,对应"ana"

8. 算法分析

  • 时间复杂度:O(n log n),主要来自后缀数组构建
  • 空间复杂度:O(n),用于存储后缀数组和高度数组
  • 相比暴力解法的O(n³)有显著优化

9. 特殊情况处理

  • 空字符串:返回0
  • 无重复子串:返回0
  • 全相同字符:返回n-1

这种方法高效地找到了最长重复子串,是处理字符串重复模式识别的经典算法。

最长重复子串(后缀数组解法) 题目描述 给定一个字符串s,找到最长的重复子串的长度。"重复"意味着在字符串中至少出现两次(可以重叠)的连续子串。要求时间复杂度优于O(n²)。 解题过程 1. 问题分析 暴力解法需要枚举所有子串O(n²)并检查出现次数O(n),总复杂度O(n³) 我们需要利用后缀数组和高度数组来优化 2. 后缀数组基础概念 后缀数组包含字符串的所有后缀按字典序排序后的起始位置。 示例:s = "banana" 后缀: 0: "banana" 1: "anana" 2: "nana" 3: "ana" 4: "na" 5: "a" 排序后的后缀数组SA:[ 5, 3, 1, 0, 4, 2 ] (对应后缀:"a", "ana", "anana", "banana", "na", "nana") 3. 高度数组概念 高度数组LCP[ i ]表示排序后相邻两个后缀的最长公共前缀长度。 对于SA = [ 5,3,1,0,4,2 ]: LCP[ 1 ]:比较"a"和"ana" → 公共前缀"a" → 长度1 LCP[ 2 ]:比较"ana"和"anana" → 公共前缀"ana" → 长度3 LCP[ 3 ]:比较"anana"和"banana" → 公共前缀"" → 长度0 LCP[ 4 ]:比较"banana"和"na" → 公共前缀"" → 长度0 LCP[ 5 ]:比较"na"和"nana" → 公共前缀"na" → 长度2 4. 关键观察 最长重复子串的长度就是高度数组中的最大值! 为什么? 排序后相邻后缀的公共前缀就是重复出现的子串 高度数组记录了这些公共前缀的长度 最大值就是最长的重复子串 5. 构建后缀数组 使用倍增算法,时间复杂度O(n log n): 6. 构建高度数组 7. 完整算法 8. 算法分析 时间复杂度:O(n log n),主要来自后缀数组构建 空间复杂度:O(n),用于存储后缀数组和高度数组 相比暴力解法的O(n³)有显著优化 9. 特殊情况处理 空字符串:返回0 无重复子串:返回0 全相同字符:返回n-1 这种方法高效地找到了最长重复子串,是处理字符串重复模式识别的经典算法。