环状DNA序列的最小表示问题 | 豆包MarsCode AI刷题

83 阅读4分钟

环状DNA序列的最小表示问题

问题描述

小C正在研究一种环状的DNA结构,它由四种碱基A、C、G、T构成。这种环状结构的特点是可以从任何位置开始读取序列,因此一个长度为 nn 的碱基序列可以有 nn 种不同的表示方式。小C的任务是从这些表示中找到字典序最小的序列,即该序列的“最小表示”。

输入

  • dna_sequence:一个由字符 'A'、'C'、'G' 和 'T' 组成的字符串,表示环状DNA序列。

输出

  • 返回该DNA序列的字典序最小表示。

示例

  • 样例1

    • 输入:dna_sequence = "ATCA"
    • 输出:"AATC"
  • 样例2

    • 输入:dna_sequence = "CGAGTC"
    • 输出:"AGTCCG"
  • 样例3

    • 输入:dna_sequence = "TTGAC"
    • 输出:"ACTTG"

思路解析

这个问题可以通过双指针和比较子串的方法来解决。具体步骤如下:

  1. 倍长字符串

    • 由于是环状结构,我们可以将原字符串拼接两次,形成一个长度为 2n2n 的字符串。这样就可以方便地处理环状结构的特性。
  2. 双指针比较

    • 使用两个指针 left 和 right,分别指向当前字典序最小子串的起始位置和下一个可能的起始位置。
    • 比较这两个指针所指向的长度为 nn 的子串,如果 right 指向的子串更小,则更新 left 为 right 的值。
    • 逐步移动 right 指针,直到遍历完所有可能的起始位置。
  3. 辅助函数

    • 定义一个辅助函数 compareSubstrings 来逐个字符比较两个长度为 nn 的子串,返回它们的字典序关系。
  4. 截取结果

    • 最后,从 left 指针的位置开始截取长度为 nn 的子串,即为所求的字典序最小表示。

代码详解

  1. 获取输入序列的长度

    java
    深色版本
    int n = dna_sequence.length();
    
    • 获取输入 DNA 序列的长度 nn。
  2. 构造倍长字符串

    java
    深色版本
    String doubled = dna_sequence + dna_sequence;
    
    • 将输入的 DNA 序列拼接两次,形成一个长度为 2n2n 的字符串。
  3. 初始化左右指针

    java
    深色版本
    int left = 0;  // 当前字典序最小子串的起始位置
    int right = 1; // 下一个起始位置,用于比较
    
    • 初始化两个指针 left 和 right,分别指向当前字典序最小子串的起始位置和下一个可能的起始位置。
  4. 使用双指针找到字典序最小的起始位置

    java
    深色版本
    while (right < n) {
        if (compareSubstrings(doubled, n, left, right) == 1) {
            left = right;
        }
        right++;
    }
    
    • 使用 while 循环遍历所有可能的起始位置。
    • 调用 compareSubstrings 方法比较 left 和 right 指针所指向的子串,如果 right 指向的子串更小,则更新 left 为 right 的值。
    • 逐步移动 right 指针,直到遍历完所有可能的起始位置。
  5. 从最小字典序位置开始截取长度为 nn 的子串

    java
    深色版本
    return doubled.substring(left, left + n);
    
    • 从 left 指针的位置开始截取长度为 nn 的子串,即为所求的字典序最小表示。
  6. 辅助方法

    java
    深色版本
    public static int compareSubstrings(String doubled, int n, int left, int right) {
        for (int i = 0; i < n; i++) {
            char leftChar = doubled.charAt(left + i);
            char rightChar = doubled.charAt(right + i);
    
            if (leftChar < rightChar) {
                return -1; // left子串更小
            } else if (leftChar > rightChar) {
                return 1; // right子串更小
            }
        }
        return 0; // 如果所有字符相等,则子串相等
    }
    
    • 定义一个辅助方法 compareSubstrings,用于逐个字符比较两个长度为 nn 的子串。
    • 如果 left 子串更小,返回 -1;如果 right 子串更小,返回 1;如果所有字符相等,返回 0

代码解答

下面是详细的 Java 代码实现,包括对每个步骤的详细解释。

public class Main {
    public static String solution(String dna_sequence) {
        // Step 1: 获取输入序列的长度
        int n = dna_sequence.length();
        
        // Step 2: 构造倍长字符串
        String doubled = dna_sequence + dna_sequence;
        
        // Step 3: 初始化左右指针
        int left = 0;  // 当前字典序最小子串的起始位置
        int right = 1; // 下一个起始位置,用于比较
        
        // Step 4: 使用双指针找到字典序最小的起始位置
        while (right < n) {
            if (compareSubstrings(doubled, n, left, right) == 1) {
                left = right;
            }
            right++;
        }
        
        // Step 5: 从最小字典序位置开始截取长度为 n 的子串
        return doubled.substring(left, left + n);
    }

    // 辅助方法,用于比较两个起点的字典序子串
    public static int compareSubstrings(String doubled, int n, int left, int right) {
        // 按照字典序逐个字符比较长度为 n 的子串
        for (int i = 0; i < n; i++) {
            char leftChar = doubled.charAt(left + i);
            char rightChar = doubled.charAt(right + i);

            if (leftChar < rightChar) {
                return -1; // left子串更小
            } else if (leftChar > rightChar) {
                return 1; // right子串更小
            }
        }
        return 0; // 如果所有字符相等,则子串相等
    }

    public static void main(String[] args) {
        // 测试用例
        System.out.println(solution("ATCA").equals("AATC"));        // 输出: true
        System.out.println(solution("CGAGTC").equals("AGTCCG"));    // 输出: true
        System.out.println(solution("TCATGGAGTGCTCCTGGAGGCTGAGTCCATCTCCAGTAG").equals("AGGCTGAGTCCATCTCCAGTAGTCATGGAGTGCTCCTGG")); // 输出: true
    }
}