问题描述
小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)]