1.问题
问题描述
小U是一位古生物学家,正在研究不同物种之间的血缘关系。为了分析两种古生物的血缘远近,她需要比较它们的DNA序列。DNA由四种核苷酸A、C、G、T组成,并且可能通过三种方式发生变异:添加一个核苷酸、删除一个核苷酸或替换一个核苷酸。小U认为两条DNA序列之间的最小变异次数可以反映它们之间的血缘关系:变异次数越少,血缘关系越近。
你的任务是编写一个算法,帮助小U计算两条DNA序列之间所需的最小变异次数。
dna1: 第一条DNA序列。dna2: 第二条DNA序列。
测试样例
样例1:
输入:dna1 = "AGT",dna2 = "AGCT"
输出:
1
样例2:
输入:dna1 = "AACCGGTT",dna2 = "AACCTTGG"
输出:
4
样例3:
输入:dna1 = "ACGT",dna2 = "TGC"
输出:
3
样例4:
输入:dna1 = "A",dna2 = "T"
输出:
1
样例5:
输入:dna1 = "GGGG",dna2 = "TTTT"
输出:
4
2.思路
这道题和之前的一道题比较相似:2024字节青训营刷题日记入营考核:【✍️ 入营考核】AI 加码,青训营 X 豆包MarsCode 技术训练营👏 欢迎 - 掘金中的第四题: DNA序列距离编辑(16) 使用动态规划的思路。
定义一个二维数组 dp,其中 dp[i][j] 表示将 dna1 的前 i 个字符转换为 dna2 的前 j 个字符所需的最小操作数
-
初始状态:
dp[0][0] = 0,因为将空字符串转换为空字符串不需要任何操作。dp[i][0] = i,因为将前 i 个字符的 dna1 转换为空字符串需要 i 次删除操作。dp[0][j] = j,因为将空字符串转换为前 j 个字符的 dna2 需要 j 次插入操作。 -
状态转移方程: 如果
dna1[i-1] == dna2[j-1](说明第i个和第j个字符相同),则dp[i][j] = dp[i-1][j-1],因为最后一个字符相同,不需要额外操作。 如果dna1[i-1] != dna2[j-1]则需要考虑三种操作:- 插入操作:dp[i][j] = dp[i][j-1] + 1
- 删除操作:dp[i][j] = dp[i-1][j] + 1
- 替换操作:dp[i][j] = dp[i-1][j-1] + 1
注意:
- df初始化时,行数为[0,len(dna1)+1),说明从前0个字符到前len(len(dna1))均可向dna2转化
- 如果
dna1[i-1] != dna2[j-1],取删除、插入、替换下步数最小的 - dna中的
i-1,j-1对应dp中的i,j
3.代码
我的代码
#include <iostream>
#include <string>
using namespace std;
int solution(std::string dna1, std::string dna2) {
// Please write your code here
int m = dna1.size();
int n = dna2.size();
int dp[m + 1][n + 1];
dp[0][0] = 0;
dp[0][n] = n;
dp[m][0] = m;
for(int i = 1; i <= m; i++){
for(int j = 1; j <= n; j++){
if (dna1[i - 1] == dna2 [j - 1]){
dp[i + 1][j + 1] = dp[i][j];
}
else {
// min函数只接受两个参数
dp[i][j] = std::min(std::min(dp[i - 1][j] + 1, dp[i][j - 1] + 1), dp[i - 1][j - 1] + 1);
}
}
}
return dp[m][n];
}
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;
}
- 初始化数组问题: 你在初始化
dp数组时使用了dp[0][0] = 0; dp[0][n] = n; dp[m][0] = m;,但这会导致数组越界,应该分别初始化dp[0][j]和dp[i][0]。 - 数组越界问题: 在动态规划更新
dp数组时,你使用了dp[i + 1][j + 1],这会导致越界错误。应该使用dp[i][j]而不是dp[i + 1][j + 1]。
#include <iostream>
#include <string>
using namespace std;
int solution(std::string dna1, std::string dna2) {
// Please write your code here
int m = dna1.size();
int n = dna2.size();
int dp[m + 1][n + 1];
dp[0][0] = 0;
for (int i = 1; i <= m; i++){
dp[i][0] = i;
}
for (int j = 1; j <= n; j++){
dp[0][j] = j;
}
for(int i = 1; i <= m; i++){
for(int j = 1; j <= n; j++){
if (dna1[i - 1] == dna2 [j - 1]){
dp[i][j] = dp[i - 1][j - 1];
}
else {
// min函数只接受两个参数
dp[i][j] = std::min(std::min(dp[i - 1][j] + 1, dp[i][j - 1] + 1), dp[i - 1][j - 1] + 1);
}
}
}
return dp[m][n];
}
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;
}
参考代码
def solution(dna1, dna2):
# Please write your code here
# 状态
# dna1长度为行数,dna2长度为列数
dp = [[0 for _ in range(len(dna2) + 1)] for _ in range(len(dna1) + 1)]
# 初始条件
dp[0][0] = 0
# dp[i][0] = i,0列等于行号
for i in range(1, len(dna1) + 1):
dp[i][0] = i
# dp[0][j] = j,0行等于列号
for j in range(1, len(dna2) + 1):
dp[0][j] = j
for i in range(1, len(dna1) + 1):
for j in range(1, len(dna2) + 1):
if dna1[i-1] == dna2[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = min(dp[i][j-1] + 1, dp[i-1][j] + 1, dp[i-1][j-1] + 1)
return dp[len(dna1)][len(dna2)]
if __name__ == "__main__":
# You can add more test cases here
print(solution("AGCTTAGC", "AGCTAGCT") == 2 )
print(solution("AGCCGAGC", "GCTAGCT") == 4)