81.古生物DNA序列血缘分析
问题描述
小U是一位古生物学家,正在研究不同物种之间的血缘关系。为了分析两种古生物的血缘远近,她需要比较它们的DNA序列。DNA由四种核苷酸A、C、G、T组成,并且可能通过三种方式发生变异:添加一个核苷酸、删除一个核苷酸或替换一个核苷酸。小U认为两条DNA序列之间的最小变异次数可以反映它们之间的血缘关系:变异次数越少,血缘关系越近。
你的任务是编写一个算法,帮助小U计算两条DNA序列之间所需的最小变异次数。
dna1: 第一条DNA序列。dna2: 第二条DNA序列。
1.思路
1.选择动态规划
1.求最小变异次数
2.长度为i-1的DNA序列最小变异次数可推长度i的DNA序列最小变异次数
##### 2.集合选择f[i][j]表示将DNA序列`dna1`的前`i`个字符转换为DNA序列`dna2`的前`j`个字符所需的最小变异次数。
##### 3.集合划分1.当两条DNA序列第i字母相等时,i可划分为A序列为i-1和B序列为j-1时相同+0次变换、A序列为i和B序列为j-1相同+1次变换、A序列为i-1和B序列为j相同+1次变换
2.当两条DNA序列第i字母不相等时,i可划分为A序列为i-1和B序列为j-1时相同+1次变换、A序列为i和B序列为j-1相同+1次变换、A序列为i-1和B序列为j相同+1次变换
2.注意事项
1.初始化
如果dna1是空字符串,那么将其变成dna2所需的变异次数是dna2的长度(因为需要插入每个字符)。
如果dna2是空字符串,那么将其变成dna1的变异次数是dna1的长度(因为需要插入每个字符)。
##### 2.边界问题判断动态规划从`i=1`和`j=1`开始,并且不会访问未定义的索引(如i+1或j+1超出范围)
3.复杂度
双重循环遍历所有的状态,即`i`从`1`到`n`,`j`从`1`到`m`,每个状态计算的复杂度为`O(1)`。因此,填充整个表格的时间复杂度为`O(n × m)`,空间复杂度为'O(nxm)'
4.代码实现
#include <bits/stdc++.h>
using namespace std;
int solution(std::string dna1, std::string dna2) {
// Please write your code here
int n = dna1.length();
int m = dna2.length();
vector<vector<int>> dp(n + 1, vector<int>(m + 1));
for (int i = 0; i <= n; i++) {
for (int j = 0; j <= m; j++) {
dp[i][j] = n + m;
}
}
for (int i = 0; i <= n; i++) {
dp[i][0] = i;
}
for (int j = 0; j <= m; j++) {
dp[0][j] = j;
}
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (dna1[i - 1] == dna2[j - 1]) {
dp[i][j] = dp[i - 1][j - 1];
} else {
dp[i][j] = dp[i - 1][j - 1] + 1;
}
dp[i][j] = min(dp[i][j], dp[i - 1][j] + 1);
dp[i][j] = min(dp[i][j], dp[i][j - 1] + 1);
}
}
return dp[n][m];
}
int main() {
// You can add more test cases here
std::cout << (solution("AGT", "AGCT") == 1) << std::endl;
std::cout << (solution("", "ACGT") == 4) << std::endl;
std::cout << (solution("GCTAGCAT", "ACGT") == 5) << std::endl;
return 0;
}