刷题打卡 | 豆包MarsCode AI 刷题

54 阅读3分钟

环状DNA序列的最小表示法

题目分析

本题需要我们找到一个环状 DNA 序列的 字典序最小表示。由于环状结构的特点,从任意位置开始读取序列都是合法的,因此一个长度为 ( n ) 的序列有 ( n ) 种可能的表示方式。我们需要从这些表示方式中找出字典序最小的那个。

以示例序列 "ATCA" 为例,它的所有表示为:

  • 从第 1 位开始:ATCA
  • 从第 2 位开始:TCAA
  • 从第 3 位开始:CAAT
  • 从第 4 位开始:AATC

在这些表示中,AATC 是字典序最小的。


解题思路

暴力法

我们可以遍历所有可能的表示方式,然后比较这些表示,选出最小的那个:

  1. 对于给定的 DNA 序列 ( S ),生成 ( n ) 种可能的表示:分别将 ( S ) 的前 ( i ) 个字符移到末尾。
  2. 比较这些表示,返回字典序最小的那个。

代码实现如下:

def solution(dna_sequence):
    n = len(dna_sequence)
    min_representation = dna_sequence  # 假设初始序列是最小的
    
    for i in range(n):
        # 将前 i 个字符移到末尾
        current_representation = dna_sequence[i:] + dna_sequence[:i]
        # 更新最小表示
        if current_representation < min_representation:
            min_representation = current_representation
    
    return min_representation

虽然这种方法能够解决问题,但其时间复杂度是 ( O(n^2) ),因为生成每种表示需要 ( O(n) ) 的时间,而我们需要比较 ( n ) 种表示。


最小表示法

为了优化时间复杂度,可以使用一种更高效的方法——最小表示法 (Minimum Lexicographic Rotation),其核心思想如下:

  1. 假设给定序列为 ( S ) 的两倍(拼接 ( S+S )),这样可以方便地模拟所有循环移位。
  2. 通过两个指针 ( i ) 和 ( j ) 来模拟从不同位置开始的循环移位:
    • ( i ) 表示当前的候选最小起点。
    • ( j ) 表示正在比较的另一个起点。
  3. 如果发现以 ( j ) 为起点的序列比 ( i ) 为起点的小,则更新 ( i )。
  4. 遍历一遍即可找到最小表示。

这种方法的时间复杂度是 ( O(n) ),更加高效。

代码实现如下:

def minimal_rotation(dna_sequence):
    n = len(dna_sequence)
    s = dna_sequence + dna_sequence  # 双倍字符串
    i, j = 0, 1  # 两个起点指针
    
    while i < n and j < n:
        k = 0  # 当前比较的字符偏移量
        while k < n and s[i + k] == s[j + k]:
            k += 1
        if k == n:
            break
        # 更新较大的起点
        if s[i + k] > s[j + k]:
            i = i + k + 1
        else:
            j = j + k + 1
        if i == j:
            j += 1
    
    # 返回最小表示
    start = min(i, j)
    return s[start:start + n]

示例运行

以下是对示例的运行结果:

示例 1: "ATCA"

print(minimal_rotation("ATCA"))  # 输出: AATC

示例 2: "CGAGTC"

print(minimal_rotation("CGAGTC"))  # 输出: AGTCCG

示例 3: "TCATGGAGTGCTCCTGGAGGCTGAGTCCATCTCCAGTAG"

print(minimal_rotation("TCATGGAGTGCTCCTGGAGGCTGAGTCCATCTCCAGTAG"))  
# 输出: AGGCTGAGTCCATCTCCAGTAGTCATGGAGTGCTCCTGG

总结

  • 暴力法思路清晰,但效率较低,适合理解和验证基本逻辑。
  • 最小表示法 利用双指针和双倍字符串的技巧,能够在 ( O(n) ) 时间内高效地找到结果。
  • 实际中,如涉及大规模序列数据,推荐使用最小表示法。

通过这个问题,我们学习到了处理环状序列和字典序最小化的技巧,扩展到其他类似问题中也很实用!