一、问题描述
问题背景
小C正在研究一种特殊的环状 DNA 结构,这种结构由四种碱基(A、C、G、T)构成。一个 DNA 序列具有环状的性质,意味着它可以从任何位置开始读取,因此一个长度为 n 的碱基序列有 n 种不同的表示方式。小C的任务是从这些表示中找到字典序最小的序列,即该序列的“最小表示”。
例如:
假设 DNA 序列为 "ATCA",它的所有表示(从不同位置读取)分别为:
ATCA, TCAA, CAAT, AATC
其中 "AATC" 是字典序最小的表示。
输入输出说明:
- 输入:一个字符串
dna_sequence,表示 DNA 序列,长度n,其中1 ≤ n ≤ 1000。 - 输出:该 DNA 序列的字典序最小的表示。
测试样例:
- 样例 1:输入
"ATCA",输出"AATC" - 样例 2:输入
"CGAGTC",输出"AGTCCG" - 样例 3:输入
"TTGAC",输出"ACTTG"
二、解题思路
这个问题的关键在于如何高效地找到一个 DNA 序列的所有可能表示,并从中找到字典序最小的表示。考虑到问题的环状特性,我们需要生成该序列的所有旋转形式并对其进行比较。
1. 字符串旋转:
一个字符串的旋转可以理解为将字符串分割并重新组合。例如,字符串 "ATCA" 的旋转包括:
ATCA, TCAA, CAAT, AATC
每一种旋转都是从不同的位置开始读取原始字符串。
2. 利用字符串双倍化:
为了获取所有的旋转,可以将原字符串与自己连接起来。例如,对于字符串 "ATCA",将其与自己连接得到 "ATCAATCA"。这样,在 doubled_sequence 中,任何一个长度为 n 的子串都对应一个旋转。例如,doubled_sequence[0:n] 是 "ATCA",doubled_sequence[1:n+1] 是 "TCAA",以此类推。
3. 字典序比较:
通过比较所有旋转,找出字典序最小的一个即可。
4. 实现步骤:
- 首先,生成字符串的双倍形式。
- 然后,遍历所有可能的旋转,逐一比较,并更新最小的旋转表示。
三、代码实现
def solution(dna_sequence: str) -> str:
# 获取 dna_sequence 的长度
n = len(dna_sequence)
# 将 dna_sequence 连接到自己,以获得所有的旋转
doubled_sequence = dna_sequence + dna_sequence
# 初始化最小的旋转为原序列
min_rotation = dna_sequence
# 比较所有可能的旋转
for i in range(1, n):
rotation = doubled_sequence[i:i + n]
if rotation < min_rotation:
min_rotation = rotation
return min_rotation
# 测试用例
if __name__ == "__main__":
print(solution("ATCA") == "AATC") # 输出 "AATC"
print(solution("CGAGTC") == "AGTCCG") # 输出 "AGTCCG"
print(solution("TTGAC") == "ACTTG") # 输出 "ACTTG"
四、知识总结
- 字符串旋转的处理:
在这个问题中,通过将原字符串与自己连接来模拟环状结构。连接后的字符串包含了所有可能的旋转,利用这种技巧可以避免手动生成每一种旋转。 - 双倍化字符串:
将字符串dna_sequence连接到自己之后,我们可以通过切片操作轻松获取每一轮旋转。doubled_sequence[i:i+n]就是从位置i开始的长度为n的子字符串。 - 字典序的比较:
对于所有的旋转,我们只需比较它们的字典序。通过遍历每一轮旋转并与当前最小旋转进行比较,最终得到字典序最小的旋转。 - 时间复杂度:
生成所有旋转的时间复杂度是O(n),每次旋转的比较操作也需要O(n),因此总时间复杂度为O(n^2)。这个复杂度对于n最大为 1000 的情况是可以接受的。
五、学习建议
- 字符串操作的技巧:
本题利用字符串的双倍化技巧,通过切片操作获取所有旋转,减少了重复生成旋转的工作。学习和理解这类字符串操作的技巧,有助于高效解决类似的问题。 - 字典序比较:
在很多字符串问题中,字典序的比较是一个常见的操作。了解如何通过简单的比较找到字典序最小的字符串,可以应用到多种不同的场景中。 - 环状结构的问题建模:
环状结构的问题,通常涉及到循环和旋转的概念。理解如何在计算机中表示和处理循环或环状的结构,是解决这类问题的关键。
六、个人总结
通过这道题,我深刻理解了如何处理环状结构和字符串旋转的问题。通过将字符串与自己连接,我们能够快速获得所有的旋转并进行比较。这种技巧不仅解决了问题,也提高了我对字符串操作的理解。对于涉及循环和旋转的算法问题,这种双倍化的思路是一个非常实用的技巧。