环状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"
- 输入:
思路解析
这个问题可以通过双指针和比较子串的方法来解决。具体步骤如下:
-
倍长字符串:
- 由于是环状结构,我们可以将原字符串拼接两次,形成一个长度为 2n2n 的字符串。这样就可以方便地处理环状结构的特性。
-
双指针比较:
- 使用两个指针
left和right,分别指向当前字典序最小子串的起始位置和下一个可能的起始位置。 - 比较这两个指针所指向的长度为 nn 的子串,如果
right指向的子串更小,则更新left为right的值。 - 逐步移动
right指针,直到遍历完所有可能的起始位置。
- 使用两个指针
-
辅助函数:
- 定义一个辅助函数
compareSubstrings来逐个字符比较两个长度为 nn 的子串,返回它们的字典序关系。
- 定义一个辅助函数
-
截取结果:
- 最后,从
left指针的位置开始截取长度为 nn 的子串,即为所求的字典序最小表示。
- 最后,从
代码详解
-
获取输入序列的长度:
java 深色版本 int n = dna_sequence.length();- 获取输入 DNA 序列的长度 nn。
-
构造倍长字符串:
java 深色版本 String doubled = dna_sequence + dna_sequence;- 将输入的 DNA 序列拼接两次,形成一个长度为 2n2n 的字符串。
-
初始化左右指针:
java 深色版本 int left = 0; // 当前字典序最小子串的起始位置 int right = 1; // 下一个起始位置,用于比较- 初始化两个指针
left和right,分别指向当前字典序最小子串的起始位置和下一个可能的起始位置。
- 初始化两个指针
-
使用双指针找到字典序最小的起始位置:
java 深色版本 while (right < n) { if (compareSubstrings(doubled, n, left, right) == 1) { left = right; } right++; }- 使用
while循环遍历所有可能的起始位置。 - 调用
compareSubstrings方法比较left和right指针所指向的子串,如果right指向的子串更小,则更新left为right的值。 - 逐步移动
right指针,直到遍历完所有可能的起始位置。
- 使用
-
从最小字典序位置开始截取长度为 nn 的子串:
java 深色版本 return doubled.substring(left, left + n);- 从
left指针的位置开始截取长度为 nn 的子串,即为所求的字典序最小表示。
- 从
-
辅助方法:
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
}
}