问题描述
小C正在研究一种环状的 DNA 结构,它由四种碱基A、C、G、T构成。这种环状结构的特点是可以从任何位置开始读取序列,因此一个长度为 n 的碱基序列可以有 n 种不同的表示方式。小C的任务是从这些表示中找到字典序最小的序列,即该序列的“最小表示”。
例如:碱基序列 ATCA 从不同位置读取可能的表示有 ATCA, TCAA, CAAT, AATC,其中 AATC 是字典序最小的表示。
测试样例
样例1:
输入:
dna_sequence = "ATCA"
输出:'AATC'
样例2:
输入:
dna_sequence = "CGAGTC"
输出:'AGTCCG'
样例3:
输入:
dna_sequence = "TTGAC"
输出:'ACTTG'
解题思路
为了找到字典序最小的序列,我们可以通过以下步骤来解决:
- 双倍字符串:将原始DNA序列复制并拼接在一起,形成一个双倍长度的字符串。这样做的好处是,我们可以通过遍历这个双倍字符串来模拟从任意位置开始的环状序列。
- 最小表示法:我们需要找到一个起始位置,使得从这个位置开始的子串是字典序最小的。这可以通过比较不同起始位置的子串来实现。
- 优化比较:直接比较所有可能的子串会非常耗时。我们可以使用Booth's算法来优化这个过程。Booth's算法通过维护一个失败数组(failure array)来减少不必要的比较,从而提高效率。
- 提取最小表示:一旦找到最小表示的起始位置,我们就可以从这个位置开始提取长度为原始序列长度的子串,这就是我们需要的字典序最小的环状序列。
代码
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")
优化思路
-
减少不必要的数组操作:
- 在Booth's算法中,
f数组(失败数组)用于记录比较过程中的状态。你可以考虑减少对f数组的初始化和更新操作,特别是在循环中频繁访问和修改数组元素时。
- 在Booth's算法中,
-
优化字符串拼接:
- 当前代码中,
s = dna_sequence * 2这一步是将原始序列复制并拼接在一起。虽然这个操作的时间复杂度是O(n),但在非常大的输入情况下,可以考虑使用生成器或迭代器来避免实际的内存复制。
- 当前代码中,
-
减少字符串比较次数:
- 在Booth's算法中,字符串比较是主要的操作。你可以考虑使用更高效的字符串比较方法,或者在比较过程中尽早退出不必要的比较。
-
内存优化:
- 如果输入序列非常大,可以考虑使用更节省内存的数据结构来存储和操作字符串。例如,使用
deque(双端队列)来模拟环状结构,而不是直接拼接字符串。
- 如果输入序列非常大,可以考虑使用更节省内存的数据结构来存储和操作字符串。例如,使用
-
并行化:
- 如果输入序列非常长,可以考虑使用并行计算来加速比较过程。Python的
concurrent.futures模块可以用来实现简单的并行化。
- 如果输入序列非常长,可以考虑使用并行计算来加速比较过程。Python的