问题描述
小C正在研究一种环状的 DNA 结构,它由四种碱基A、C、G、T构成。这种环状结构的特点是可以从任何位置开始读取序列,因此一个长度为 n 的碱基序列可以有 n 种不同的表示方式。小C的任务是从这些表示中找到字典序最小的序列,即该序列的“最小表示”。 例如:碱基序列 ATCA 从不同位置读取可能的表示有 ATCA, TCAA, CAAT, AATC,其中 AATC 是字典序最小的表示。
测试样例
样例1:
输入:dna_sequence = "ATCA" 输出:'AATC'
样例2: 输入:dna_sequence = "CGAGTC" 输出:'AGTCCG'
样例3: 输入:dna_sequence = "TTGAC" 输出:'ACTTG'
具体思路
要解决这个问题,我们需要找到给定环状 DNA 序列的最小字典序表示。一个有效的方法是将序列自身拼接一次,然后在这个双倍长度的字符串中寻找长度为 n 的子串,这个子串就是原序列的所有可能表示中的字典序最小的那个。
具体步骤如下:
- 拼接字符串:首先,我们将输入的 DNA 序列
dna_sequence拼接两次,得到一个新的字符串double_sequence。这样做的目的是模拟环状结构,使得可以从任何位置开始读取序列。 - 初始化最小序列:我们假设第一个长度为 n 的子串是最小的,并将其存储在
min_sequence中。 - 遍历查找最小字典序子串:我们从第二个字符开始遍历
double_sequence,每次提取长度为 n 的子串,并与当前的min_sequence进行比较。如果新的子串更小,则更新min_sequence。 - 返回结果:最后返回找到的最小字典序子串。
代码
#include <iostream>
#include <string>
std::string solution(std::string dna_sequence) {
int n = dna_sequence.length();
std::string double_sequence = dna_sequence + dna_sequence;
std::string min_sequence = double_sequence.substr(0, n);
for (int i = 1; i < n; ++i) {
std::string current_sequence = double_sequence.substr(i, n);
if (current_sequence < min_sequence) {
min_sequence = current_sequence;
}
}
return min_sequence;
}
int main() {
// You can add more test cases here
std::cout << (solution("ATCA") == "AATC") << std::endl;
std::cout << (solution("CGAGTC") == "AGTCCG") << std::endl;
std::cout << (solution("TCATGGAGTGCTCCTGGAGGCTGAGTCCATCTCCAGTAG") == "AGGCTGAGTCCATCTCCAGTAGTCATGGAGTGCTCCTGG") << std::endl;
return 0;
}
解释:
-
拼接字符串:首先,我们将输入的 DNA 序列
dna_sequence拼接两次,形成double_sequence。这样做的目的是模拟环状结构,使得可以从任何位置开始读取序列。 -
初始化最小序列:我们假设第一个长度为 n 的子串是最小的,并将其存储在
min_sequence中。 -
遍历查找最小字典序子串:我们从第二个字符开始遍历
double_sequence,每次提取长度为 n 的子串,并与当前的min_sequence进行比较。如果新的子串更小,则更新min_sequence。 -
返回结果:最后返回找到的最小字典序子串。
总结
1. 理解问题的本质
环状结构的特性:DNA 序列是环状的,这意味着可以从任何位置开始读取序列。因此,我们需要找到所有可能的表示方式中的字典序最小的那个。 拼接字符串的方法:通过将序列自身拼接一次,我们可以模拟环状结构,从而简化问题的处理。
2. 算法设计的重要性
遍历与比较:通过遍历双倍长度的字符串并比较子串,我们能够有效地找到字典序最小的子串。这种方法的时间复杂度为 O(n^2),对于较短的 DNA 序列来说是可以接受的。 优化空间:虽然直接拼接字符串增加了一些空间开销,但这种开销是可控的,并且大大简化了逻辑实现。
3. 编码实践
字符串操作:熟悉 C++ 中字符串的操作方法,如 substr,对于解决此类问题非常有帮助。
边界条件:注意处理各种边界条件,例如空字符串或极短的字符串,确保代码的健壮性。
4. 测试与验证
多测试用例:编写多个测试用例来验证代码的正确性,包括普通情况和边界情况。 调试技巧:学会使用调试工具和方法,快速定位和修复代码中的错误。
5. 性能考虑
时间复杂度:虽然当前的解决方案在大多数情况下是有效的,但对于非常大的输入数据,可能需要进一步优化算法以提高效率。
空间复杂度:在保证功能正确的前提下,尽量优化空间使用,避免不必要的内存消耗。