「81.古生物DNA序列血缘分析」
问题描述 小U作为古生物学家,在研究不同古生物的血缘关系时,需通过比较它们的DNA序列来分析。DNA由A、C、G、T四种核苷酸组成,且存在添加、删除、替换一个核苷酸这三种变异方式。任务是编写算法计算两条给定DNA序列(dna1和dna2)之间所需的最小变异次数,变异次数越少意味着血缘关系越近。
思路解析
- 理解变异方式与目标 明确DNA序列可通过添加、删除、替换核苷酸这三种方式发生变异,我们的目标是找到使两条DNA序列相同所需的最少变异操作次数,以此来反映它们之间的血缘关系。
- 确定解题方法 可以使用动态规划的思想来解决此问题。通过构建一个二维数组来记录在不同子序列情况下的最小变异次数。具体来说,对于dna1的前i个字符和dna2的前j个字符组成的子序列,我们逐步计算使其相同的最小变异次数。
解题步骤
- 创建二维数组:
- 创建一个大小为(len(dna1) + 1)×(len(dna2) + 1)的二维数组dp,用于存储中间结果。其中dp[i][j]表示将dna1的前i个字符和dna2的前j个字符变为相同所需的最小变异次数。
- 初始化dp数组的边界情况:
- dp[0][0] = 0,即空的dna1序列和空的dna2序列不需要任何变异操作,变异次数为0。
- 对于i > 0,dp[i][0] = i,表示将dna1的前i个字符变为空序列(即删除i个字符)所需的变异次数为i。
- 对于j > 0,dp[0][j] = j,表示将空的dna1序列变为dna2的前j个字符(即添加j个字符)所需的变异次数为j。
- 填充二维数组:
- 从dp[1][1]开始,通过两层循环遍历dna1和dna2的字符:
- 外层循环遍历dna1的字符,变量i从1到len(dna1)。
- 内层循环遍历dna2的字符,变量j从1到len(dna2)。
- 对于每个dp[i][j],根据dna1的第i个字符和dna2的第j个字符是否相同来分情况讨论:
- 如果dna1[i - 1] == dna2[j - 1](注意数组下标从0开始,所以这里是i - 1和j - 1),则dp[i][j] = dp[i - 1][j - 1],即当前字符相同,不需要进行任何变异操作,最小变异次数与前一个子序列相同。
- 如果dna1[i - 1]!= dna2[j - 1],则需要考虑三种变异操作,取最小值:
- 替换操作:dp[i][j] = dp[i - 1][j - 1] + 1,即在前一个子序列基础上替换当前字符,变异次数加1。
- 删除操作:dp[i][j] = dp[i - 1][j] + 1,即删除dna1的第i个字符,变异次数加1。
- 添加操作:dp[i][j] = dp[i][j - 1] + 1,即添加一个字符使其与dna2的第j个字符相同,变异次数加1。
- 最终dp[i][j]取这三种情况中的最小值。
- 从dp[1][1]开始,通过两层循环遍历dna1和dna2的字符:
- 获取结果:
- 填充完二维数组后,dp[len(dna1)][len(dna2)]的值就是将整个dna1序列和整个dna2序列变为相同所需的最小变异次数,将其作为最终答案返回。
代码实现
def min_mutation_times(dna1, dna2):
m = len(dna1)
n = len(dna2)
dp = [[0] * (n + 1) for _ in range(m + 1)]
# 初始化边界情况
for i in range(m + 1):
dp[i][0] = i
for j in range(n + 1):
dp[0][j] = j
# 填充二维数组
for i in range(1, m + 1):
for j in range(1, n + 1):
if dna1[i - 1] == dna2[j - 1]:
dp[i][j] = dp[i - 1][j - 1]
else:
replace = dp[i - 1][j - 1] + 1
delete = dp[i - 1][j] + 1
add = dp[i][j - 1] + 1
dp[i][j] = min(replace, delete, add)
return dp[m][n]
代码解读
- 首先定义了函数
min_mutation_times,它接受两条DNA序列dna1和dna2作为参数。 - 在函数内部,先获取两条DNA序列的长度m和n。
- 然后创建了二维数组dp,并按照上述步骤初始化了边界情况。
- 接着通过两层循环遍历dna1和dna2的字符,根据字符是否相同分情况填充dp数组。
- 最后返回dp[m][n],即两条DNA序列变为相同所需的最小变异次数。
复杂度分析
- 时间复杂度:
- 外层循环遍历dna1的长度m次,内层循环对于每个外层循环的情况遍历dna2的长度n次,所以总共需要执行m×n次循环操作。在每次循环中,主要操作是比较字符和取最小值,时间复杂度可看作常数时间。因此,时间复杂度为,其中m是dna1的长度,n是dna2的长度。
- 时间复杂度:
- 主要的空间消耗在于创建的二维数组dp,其大小为(m + 1)×(n + 1),所以空间复杂度为。