一、问题描述
小C正在研究一种环状 DNA 结构,它由四种碱基 (A)、(C)、(G)、(T) 构成。这种环状结构的特点是可以从任何位置开始读取序列,因此一个长度为 (n) 的碱基序列可以有 (n) 种不同的表示方式。小C的任务是从这些表示中找到字典序最小的序列,即该序列的“最小表示”。
示例
-
输入:
dna_sequence = "ATCA"- 环状旋转可能的表示有:
ATCA、TCAA、CAAT、AATC - 字典序最小的是
AATC - 输出:
"AATC"
- 环状旋转可能的表示有:
-
输入:
dna_sequence = "CGAGTC"- 环状旋转可能的表示有:
CGAGTC、GAGTCC、AGTCCG、GTCCGA、TCCGAG、CCGAGT - 字典序最小的是
AGTCCG - 输出:
"AGTCCG"
- 环状旋转可能的表示有:
二、解决思路
由于这个序列是环状的,所以可以从任意位置开始读取,产生不同的旋转序列。我们需要从这些旋转中找到字典序最小的表示。
假设输入序列长度为 (n),我们可以通过以下方法来寻找其最小字典序的表示。
三、解题方法
1. 暴力法(不推荐)
我们可以遍历每一个可能的旋转,将其字典序与当前最小的旋转进行比较。尽管这种方法能够找到最小表示,但复杂度为 (O(n^2)),对长序列效率较低。
2. 双倍字符串法
将原始字符串与自己拼接起来形成一个双倍字符串。双倍字符串的特点是我们可以方便地在其中查找所有旋转情况。
对于 DNA 序列 (S) 的长度为 (n),将 (S) 与自身拼接得到 (S+S),形成长度为 (2n) 的新字符串。在这个新字符串中,从 (0) 到 (n-1) 的每一个位置作为起始位置的 (n) 长度子串,都是原字符串的一种旋转。通过遍历这些旋转,寻找字典序最小的旋转位置,就可以得到答案。
双倍字符串的复杂度是 (O(n^2)),通过直接比较不同子串效率不够高,尤其在面对长序列时。因此,我们引入更高效的算法——Booth’s 算法。
3. Booth's 算法(推荐)
Booth's 算法是一种能够在线性时间 (O(n)) 内找到字符串最小表示的方法。其核心思想是通过维护一个失败数组,快速跳过不符合的前缀,从而确定最小字典序的起始位置。
Booth’s 算法最常用在字符串旋转相关的问题中,比如找到旋转字符串中的最小表示。
四、Booth's 算法实现步骤
- 构造双倍字符串:将原 DNA 序列 (S) 拼接为 (S+S),并定义长度 (2n) 的新字符串
s。 - 初始化变量:
f:失败数组,初始化为 ([-1] * 2n)k:当前找到的最小表示的起始位置,初始为 0
- 遍历字符串:
- 从位置 (j = 1) 开始,逐个字符与最小表示位置进行比较。
- 使用失败数组来记录匹配失败的位置,跳过重复的比较。
- 返回结果:
- 从最小表示的起始位置 (k) 提取长度为 (n) 的子串,即为所求字典序最小表示。
五、代码实现
以下是使用 Booth’s 算法求解最小表示的 Python 实现。
def solution(dna_sequence):
# 使用 Booth's 算法来找到字典序最小的旋转起始位置
s = dna_sequence * 2 # 双倍字符串
n = len(dna_sequence)
f = [-1] * (2 * n) # 失败数组
k = 0 # 最小表示的起始位置
for j in range(1, 2 * n):
sj = s[j]
i = f[j - k - 1]
while i != -1 and sj != s[k + i + 1]:
if sj < s[k + i + 1]:
k = j - i - 1
i = f[i]
if sj != s[k + i + 1]: # 更新 k
if sj < s[k]:
k = j
f[j - k] = -1
else:
f[j - k] = i + 1
# 提取从最小起始位置开始的 n 长度的子串,即最小字典序表示
return s[k:k + n]
if __name__ == "__main__":
# You can add more test cases here
print(solution("ATCA") == "AATC")
print(solution("CGAGTC") == "AGTCCG")
print(solution("TCATGGAGTGCTCCTGGAGGCTGAGTCCATCTCCAGTAG") == "AGGCTGAGTCCATCTCCAGTAGTCATGGAGTGCTCCTGG")
六、复杂度分析
- 时间复杂度:(O(n)),Booth's 算法的效率来源于线性遍历字符串,并通过失败数组跳过多余的比较。
- 空间复杂度:(O(n)),只需额外存储双倍字符串和失败数组。
七、总结
Booth's 算法为我们提供了一种高效且简单的方法来解决最小表示问题。它通过对字符串的双倍扩展和失败数组的维护,能够在 (O(n)) 时间内找到字符串的字典序最小表示。