AI刷题 81.古生物DNA序列血缘分析 | 豆包MarsCode AI刷题

39 阅读3分钟

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;
}