一、题目背景
小 U 是一位古生物学家,为分析两种古生物的血缘远近,需要比较它们的 DNA 序列。DNA 由 A、C、G、T 四种核苷酸组成,且可通过添加、删除或替换一个核苷酸发生变异。小 U 认为两条 DNA 序列之间的最小变异次数可反映它们的血缘关系,变异次数越少,血缘越近。
二、问题分析
-
输入与输出
- 输入是两条 DNA 序列
dna1和dna2。 - 输出是将
dna1转换为dna2所需的最小变异次数。
- 输入是两条 DNA 序列
-
测试样例分析
- 样例 1:输入
dna1 = "AGT",dna2 = "AGCT",输出 1。因为只需在dna1中添加一个C即可得到dna2。 - 样例 2:输入
dna1 = "",dna2 = "ACGT",输出 4。因为需要添加 4 个核苷酸才能从空的dna1得到dna2。 - 样例 3:输入
dna1 = "GCTAGCAT",dna2 = "ACGT",输出 5。说明需要进行 5 次变异操作才能将dna1转换为dna2。
- 样例 1:输入
三、解题思路
-
采用动态规划的方法解决此问题。
-
定义一个二维数组
dp,其中dp[i][j]表示dna1的前i个字符转换为dna2的前j个字符所需的最小变异次数。 -
初始化
dp数组的第一行和第一列:- 当
i = 0时(即dna1为空),dp[0][j]=j,因为需要j次添加操作。 - 当
j = 0时(即dna2为空),dp[i][0]=i,因为需要i次删除操作。
- 当
-
对于
dp数组中的其他元素:- 如果
dna1的第i个字符和dna2的第j个字符相同,那么dp[i][j]=dp[i - 1][j - 1],因为不需要进行变异操作。 - 如果不相同,则选择添加、删除、替换操作中的最小值,即
dp[i][j]=1 + min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1])。 - 其中
dp[i - 1][j]表示删除操作,dp[i][j - 1]表示添加操作,dp[i - 1][j - 1]表示替换操作。
- 如果
四、代码分析
def solution(dna1, dna2):
m = len(dna1)
n = len(dna2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
for i in range(m + 1):
for j in range(n + 1):
if i == 0:
dp[i][j] = j
elif j == 0:
dp[i][j] = i
elif dna1[i - 1] == dna2[j - 1]:
dp[i][j] = dp[i - 1][j - 1]
else:
dp[i][j] = 1 + min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1])
return dp[m][n]
- 首先获取
dna1和dna2的长度m和n。 - 创建二维数组
dp,并初始化第一行和第一列。 - 通过两层循环遍历
dp数组,根据dna1和dna2对应位置字符是否相同来更新dp[i][j]的值。 - 最后返回
dp[m][n],即两条完整 DNA 序列转换所需的最小变异次数。
五、复杂度分析
-
时间复杂度
- 有两层嵌套的循环,外层循环次数取决于
dna1的长度m,内层循环次数取决于dna2的长度n。所以时间复杂度为,其中m和n分别是dna1和dna2的长度。
- 有两层嵌套的循环,外层循环次数取决于
-
空间复杂度
- 创建了一个大小为的二维数组
dp来存储中间结果,所以空间复杂度为。
- 创建了一个大小为的二维数组
六、总结与收获
-
动态规划思想的深化
- 通过解决这个问题,对动态规划有了更深入的理解。动态规划通过将大问题分解为子问题,并存储子问题的解来避免重复计算,在本题中通过
dp数组实现了这一点。
- 通过解决这个问题,对动态规划有了更深入的理解。动态规划通过将大问题分解为子问题,并存储子问题的解来避免重复计算,在本题中通过
-
问题转化能力
- 学会了将实际的生物序列比较问题转化为一个可以用算法解决的问题。这种将实际问题抽象化、模型化的能力对于解决其他类似的复杂问题很有帮助。
-
对字符串处理的新认识
- 在处理 DNA 序列这种字符串问题时,了解到除了常见的字符串操作方法外,还可以利用动态规划来计算字符串之间的差异程度,这为处理其他字符串相关的算法问题提供了新的思路。