青训营X豆包MarsCode 学习笔记:环形 DNA 序列的最小表示法|豆包MarsCode AI刷题

32 阅读4分钟

一、问题描述

问题背景
小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"

四、知识总结

  1. 字符串旋转的处理:
    在这个问题中,通过将原字符串与自己连接来模拟环状结构。连接后的字符串包含了所有可能的旋转,利用这种技巧可以避免手动生成每一种旋转。
  2. 双倍化字符串:
    将字符串 dna_sequence 连接到自己之后,我们可以通过切片操作轻松获取每一轮旋转。doubled_sequence[i:i+n] 就是从位置 i 开始的长度为 n 的子字符串。
  3. 字典序的比较:
    对于所有的旋转,我们只需比较它们的字典序。通过遍历每一轮旋转并与当前最小旋转进行比较,最终得到字典序最小的旋转。
  4. 时间复杂度:
    生成所有旋转的时间复杂度是 O(n),每次旋转的比较操作也需要 O(n),因此总时间复杂度为 O(n^2)。这个复杂度对于 n 最大为 1000 的情况是可以接受的。

五、学习建议

  1. 字符串操作的技巧:
    本题利用字符串的双倍化技巧,通过切片操作获取所有旋转,减少了重复生成旋转的工作。学习和理解这类字符串操作的技巧,有助于高效解决类似的问题。
  2. 字典序比较:
    在很多字符串问题中,字典序的比较是一个常见的操作。了解如何通过简单的比较找到字典序最小的字符串,可以应用到多种不同的场景中。
  3. 环状结构的问题建模:
    环状结构的问题,通常涉及到循环和旋转的概念。理解如何在计算机中表示和处理循环或环状的结构,是解决这类问题的关键。

六、个人总结

通过这道题,我深刻理解了如何处理环状结构和字符串旋转的问题。通过将字符串与自己连接,我们能够快速获得所有的旋转并进行比较。这种技巧不仅解决了问题,也提高了我对字符串操作的理解。对于涉及循环和旋转的算法问题,这种双倍化的思路是一个非常实用的技巧。