算法学习笔记:5.后缀数组——从原理到实战,涵盖 LeetCode 与考研 408 例题

发布于:2025-07-05 ⋅ 阅读:(15) ⋅ 点赞:(0)

在计算机科学领域,字符串处理是一个重要的研究方向,而后缀数组作为处理字符串的强大工具,在诸多算法问题和实际应用中发挥着关键作用。无论是在 LeetCode 等算法竞赛平台,还是计算机考研 408 的考试大纲中,后缀数组相关的知识都占据着一席之地。

后缀数组的基本概念

后缀数组(Suffix Array),顾名思义,是由字符串的所有后缀按字典序排序后得到的数组。假设我们有一个字符串 S = "banana",它的所有后缀如下:

  • banana
  • anana
  • nana
  • ana
  • na
  • a

将这些后缀按字典序排序后,得到的后缀数组为 [5, 3, 1, 0, 4, 2],这个数组中的每个元素表示对应后缀在原字符串中的起始位置。例如,后缀数组中第一个元素 5 表示排序后的第一个后缀 a 在原字符串 "banana" 中从位置 5 开始。

后缀数组的算法思想

构建后缀数组的方法有多种,常见的有倍增算法和 DC3 算法。这里我们主要介绍倍增算法,它的核心思想基于分治策略和倍增优化。

2.1 倍增算法的基本步骤

  1. 初始化:将字符串的每个字符看作一个长度为 1 的后缀,对这些后缀进行排序。在这一步,我们可以根据字符的 ASCII 码值进行排序,得到一个初步的排序结果。
  1. 倍增排序:每次将后缀的长度翻倍,利用上一次排序的结果,对长度翻倍后的后缀进行排序。具体来说,我们将每个后缀拆分为两个子串,分别是前半部分和后半部分,根据这两个子串的排序结果来确定整个后缀的排序。
  1. 重复倍增:不断重复上述倍增排序的过程,直到所有后缀都被正确排序。由于字符串的长度为 n,通过 O(log n) 次倍增操作,我们就能完成后缀数组的构建。

下面通过一个示例来详细说明倍增算法的过程。假设我们有字符串 S = "abab":

  • 第一轮:将每个字符看作一个后缀,即 ["a", "b", "a", "b"],按字典序排序后得到 [0, 2, 1, 3]。
  • 第二轮:将后缀长度翻倍,考虑长度为 2 的后缀 ["ab", "ba", "ab"]。此时,利用第一轮的排序结果,我们可以更高效地对这些后缀进行排序,得到 [0, 2, 1](这里的索引表示原字符串中后缀的起始位置)。
  • 第三轮:继续将后缀长度翻倍,此时后缀为 ["abab"],排序后得到最终的后缀数组 [0]。

**

2.2 后缀数组与排名数组

在构建后缀数组的过程中,我们还会涉及到一个重要的概念 —— 排名数组(Rank Array)。排名数组与后缀数组是互逆的关系。后缀数组记录的是排序后每个后缀在原字符串中的起始位置,而排名数组记录的是原字符串中每个后缀在排序后的后缀数组中的位置。

例如,对于字符串 S = "banana",其后缀数组为 [5, 3, 1, 0, 4, 2],那么排名数组为 [3, 2, 5, 1, 4, 0]。也就是说,原字符串中从位置 0 开始的后缀 "banana" 在排序后的后缀数组中排名第 3。

后缀数组的解题思路

后缀数组可以用于解决许多字符串相关的问题,如最长公共前缀(Longest Common Prefix,LCP)问题、字符串匹配问题、重复子串问题等。下面我们以最长公共前缀问题为例,介绍后缀数组的解题思路。

3.1 最长公共前缀问题

最长公共前缀问题是指在一个字符串的所有后缀中,找出最长的公共前缀。利用后缀数组解决该问题的关键在于,排序后的后缀数组中相邻的后缀往往具有较长的公共前缀。

我们可以通过构建后缀数组和计算高度数组(Height Array)来解决最长公共前缀问题。高度数组 height[i] 表示后缀数组中第 i 个后缀和第 i - 1 个后缀的最长公共前缀长度。

计算高度数组的方法有多种,其中一种常用的方法是 Kasai 算法。Kasai 算法的基本思路是利用排名数组,从左到右依次计算每个后缀与前一个后缀的最长公共前缀长度。

具体步骤如下:

  1. 初始化 height[0] = 0。
  1. 对于每个后缀 i(1 <= i < n),根据排名数组找到其在后缀数组中的位置 k。
  1. 计算 height[k],即后缀数组中第 k 个后缀和第 k - 1 个后缀的最长公共前缀长度。

通过计算高度数组,我们可以快速找到任意两个后缀的最长公共前缀长度,从而解决最长公共前缀问题。

LeetCode 例题及 Java 代码实现

例题:后缀排序(LeetCode 1163)

给你一个字符串 s ,请你从 s 中找出 最长的超序子序列 ,并返回该子序列。如果存在多个最长的超序子序列,返回字典序最小的一个 。

超序子序列 定义为:对于一个字符串 t ,可以删除 s 中的某些字符,使其变为 t ,但不能改变剩余字符的相对顺序。

解题思路

本题可以利用后缀数组的思想来解决。首先,我们生成字符串 s 的所有后缀,并对这些后缀进行排序。排序后的第一个后缀就是字典序最小的最长超序子序列。

Java 代码实现
import java.util.ArrayList;

import java.util.Collections;

import java.util.List;

public class LongestDecomposition {

    public String lastSubstring(String s) {

        List<String> suffixes = new ArrayList<>();

        for (int i = 0; i < s.length(); i++) {

            suffixes.add(s.substring(i));

        }

        Collections.sort(suffixes);

        return suffixes.get(suffixes.size() - 1);

    }

}

例题:重复的 DNA 序列(LeetCode 187)

所有 DNA 都由一系列缩写为 'A','C','G' 和 'T' 的核苷酸组成,例如:"ACGAATTCCG"。在研究 DNA 时,识别 DNA 中的重复序列有时会对研究非常有帮助。

编写一个函数来找出所有目标子串,目标子串的长度为 10,且在 DNA 字符串 s 中出现次数超过一次。

解题思路

我们可以将长度为 10 的子串看作是字符串的后缀(这里可以通过滑动窗口的方式获取这些后缀),然后利用后缀数组或哈希表来统计每个子串出现的次数,找出出现次数超过一次的子串。

Java 代码实现

import java.util.ArrayList;

import java.util.HashMap;

import java.util.List;

import java.util.Map;

public class RepeatedDNASequences {

    public List<String> findRepeatedDnaSequences(String s) {

        List<String> result = new ArrayList<>();

        if (s.length() < 10) {

            return result;

        }

        Map<String, Integer> map = new HashMap<>();

        for (int i = 0; i <= s.length() - 10; i++) {

            String sub = s.substring(i, i + 10);

            map.put(sub, map.getOrDefault(sub, 0) + 1);

            if (map.get(sub) == 2) {

                result.add(sub);

            }

        }

        return result;

    }

}

后缀数组与考研 408

在计算机考研 408 中,数据结构和算法是重要的考试内容,后缀数组虽然不是每年都会直接考查,但与之相关的字符串处理、排序算法、分治思想等知识点是考试的重点。

  1. 数据结构基础:后缀数组的构建和使用涉及到数组、字符串等基本数据结构。在复习过程中,需要熟练掌握数组的操作和字符串的存储方式,理解它们在内存中的表示形式。
  1. 排序算法:后缀数组的构建依赖于排序算法,无论是倍增算法中对后缀的排序,还是在解决实际问题时对后缀数组的处理,都需要对排序算法有深入的理解。考研 408 中常见的排序算法,如快速排序、归并排序、堆排序等,其思想和时间复杂度分析都与后缀数组的学习密切相关。
  1. 分治思想:倍增算法是后缀数组构建的核心算法,它充分体现了分治思想。在考研复习中,要掌握分治算法的设计和分析方法,理解如何将一个大问题分解为多个子问题,并通过解决子问题来得到原问题的解。
  1. 字符串处理:后缀数组主要用于解决字符串相关的问题,考研 408 中也会涉及到字符串的模式匹配、子串查找等问题。学习后缀数组可以帮助我们拓宽解决字符串问题的思路,掌握更高效的算法方法。

通过结合后缀数组的学习,我们可以更好地理解和掌握考研 408 中的相关知识点,提高自己的算法设计和分析能力。

总结

后缀数组作为一种强大的字符串处理工具,其算法思想和解题思路在算法竞赛和计算机考研中都具有重要的应用价值。通过本文对后缀数组的概念、算法思想、解题思路的详细介绍,以及结合 LeetCode 例题和 Java 代码实现,相信大家对后缀数组有了更深入的理解。同时,我们也探讨了后缀数组与考研 408 知识点的联系,希望能为考研学子的复习提供帮助。

希望本文能够帮助读者更深入地理解后缀数组算法,并在实际项目中发挥其优势。谢谢阅读!


希望这份博客能够帮助到你。如果有其他需要修改或添加的地方,请随时告诉我。