做题笔记:古生物DNA序列血缘分析| 豆包MarsCode AI 刷题

98 阅读3分钟

问题描述

小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

解题思路

  • 这个问题的本质是计算两个字符串之间的编辑距离,也称为莱文斯坦距离,所以使用动态规划
  • 计算两个字符串之间的最小变异次数,可使用的方法包括插入、删除或替换,将一个字符串转换为另一个字符串,并且操作次数尽可能小
  • 如果dna1[i]==dna2[j]那么dp[i][j]就等于dp[i-1][j-1]。如果不相等则需要通过插入,删除,替换三种操作

代码实现

第一步

初始化dp数组dp[i][j] :以下标i - 1为结尾的dna1,和以下标j - 1为结尾的dna2,相等时最少的操作次数。 之所以不将表示为dp[i][j] :以下标i为结尾的dna1,和以下标j 为结尾的dna2,相等时最少的操作次数,原因为实现起来就麻烦一点,需要单独处理初始化部分。

dp := make([][]int, len(dna1)+1)
	for i := range dp {
		dp[i] = make([]int, len(dna2)+1)
	}

第二步

初始化边界条件,由于dp数组的定义,所以dp[i][0]表示将 dna1 的前 i 个字符转换为空字符串,dp[0][j]将空字符串转换为 dna2 的前 j 个字符

	for i := range dp {
		dp[i][0] = i 
	}
	for j := range dp[0] {
		dp[0][j] = j 
	}

第三步

遍历dna1与dna2,不必在乎谁是内层,谁是外层。当dna1[i-1] 等于dna2[j-1]时说明不需要进行操作,dp[i][j] = dp[i-1][j-1]。当dna1[i-1] 不等于dna2[j-1]时说明需要进行操作,具体操作为删除dp[i-1][j]+1,插入dp[i][j-1]+1,替换dp[i-1][j-1]+1,这三个中最小的一个。

for i := 1; i <= len(dna1); i++ {
		for j := 1; j <= len(dna2); j++ {
			if dna1[i-1] == dna2[j-1] {
				dp[i][j] = dp[i-1][j-1] 
			} else {
				dp[i][j] = min(
					dp[i-1][j]+1,  
					dp[i][j-1]+1, 
					dp[i-1][j-1]+1,
				)
			}
		}
	}

第四步

返回结果

return dp[len(dna1)][len(dna2)]