题目解析
本题的目标是从给定的环状 DNA 序列中找到字典序最小的表示。环状结构的特点是可以从任意位置开始读取,因此一个长度为 的序列会有 种不同的表示方式。本质上,这是一个寻找循环字符串的“最小表示”的问题。
假设 DNA 序列是 "ATCA",我们可以通过旋转生成所有可能的表示:ATCA、TCAA、CAAT 和 AATC。这些表示按字典序排序后,AATC 是最小的。找到这样的最小表示是本题的核心目标。
对于直接生成所有旋转并排序的暴力解法,虽然简单直观,但时间复杂度较高,约为 ,因为需要生成 个表示并对其进行排序。在实际应用中,DNA 序列可能很长,这种方法效率低下。因此,我们需要通过更高效的算法来解决这个问题。
解题思路
- 暴力法:
• 生成所有 种表示,使用排序找出字典序最小的表示。
• 时间复杂度:生成 种旋转表示需要 ,排序需要 。总复杂度约为 ,在大规模数据下不可取。
- 双倍字符串法(MarsCode AI推荐):
• 将原始字符串拼接自身,如 DNA 序列 "ATCA" 变为 "ATCAATCA"。
• 最小表示一定是原字符串的一个子串,因此只需在双倍字符串中找到字典序最小的长度为 的子串即可。
• 使用线性扫描找到该子串的起始位置,从而直接获得最小表示。
• 时间复杂度:,在所有可能的起点中只扫描一次。
代码详解
- 输入与初始化:
• 输入是一个 DNA 序列 dna,其长度为 。
• 首先将 dna 与自身拼接成一个双倍字符串 doubled_dna,长度为 。这样可以方便地模拟环状结构的旋转。
• 变量 min_start 记录当前字典序最小子串的起始位置,初始值为 0。
- 循环遍历:
• 从 1 开始遍历每个可能的起始位置 。
• 比较 doubled_dna[i:i+n](从位置 开始长度为 的子串)与 doubled_dna[min_start:min_start+n](当前最小子串)。
• 如果新的子串更小,则更新 min_start。
- 结果返回:
• 遍历完成后,min_start 即为字典序最小子串的起始位置。
• 返回从 min_start 开始的长度为 的子串。
- 测试用例:
• 输入 ATCA 时,遍历发现起始位置为 2 时子串最小,因此返回 AATC。
• 对其他测试用例如 GATTACA 和 CTAGCT,代码同样适用。
复杂度分析
- 时间复杂度:
• 构造双倍字符串需要 。
• 遍历 个起始位置,每次比较两个长度为 的子串,比较操作时间为 。
• 总复杂度为 。
- 空间复杂度:
• 额外空间主要是双倍字符串,复杂度为 。
优化与拓展
• 如果输入规模更大,可以考虑使用 Booth 算法,它在实践中更加高效且无需构造双倍字符串。
• 此外,若 DNA 序列包含噪声(如非法碱基符号),可在输入阶段加入验证逻辑以增强代码的鲁棒性。