问题描述
小U是一位古生物学家,正在研究不同物种之间的血缘关系。为了分析两种古生物的血缘远近,她需要比较它们的DNA序列。DNA由四种核苷酸A、C、G、T组成,并且可能通过三种方式发生变异:添加一个核苷酸、删除一个核苷酸或替换一个核苷酸。小U认为两条DNA序列之间的最小变异次数可以反映它们之间的血缘关系:变异次数越少,血缘关系越近。
你的任务是编写一个算法,帮助小U计算两条DNA序列之间所需的最小变异次数。
dna1: 第一条DNA序列。dna2: 第二条DNA序列。
解题思路
-
初始化边界条件:
- 当一条DNA序列为空时,将其转换成另一条非空序列所需的编辑操作次数等于非空序列的长度(因为我们需要插入所有的字符)。
- 同理,当另一条序列为空时,将非空序列转换成空序列所需的编辑操作次数等于非空序列的长度(因为我们需要删除所有的字符)。
-
构建动态规划表:
-
创建一个二维数组
dp,其中dp[i][j]表示将长度为i的dna1的前缀转换成长度为j的dna2的前缀所需的最小编辑操作次数。 -
填充这个表,对于
dna1的每一个字符和dna2的每一个字符,考虑以下情况:-
如果
dna1[i-1] == dna2[j-1],即当前字符相同,则不需要编辑操作,dp[i][j] = dp[i-1][j-1]。 -
如果
dna1[i-1] != dna2[j-1],即当前字符不同,则需要考虑以下三种编辑操作,并取最小值:- 插入一个字符:
dp[i][j-1] + 1 - 删除一个字符:
dp[i-1][j] + 1 - 替换一个字符:
dp[i-1][j-1] + 1
- 插入一个字符:
-
-
-
返回结果:
dp[m][n]就是将整个dna1转换为整个dna2所需的最小编辑操作次数,其中m和n分别是dna1和dna2的长度。
代码实现
def solution(dna1, dna2):
# 使用动态规划来计算最小变异次数
m, n = len(dna1), 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:
dp[i][j] = min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]) + 1
return dp[m][n]
知识点总结
动态规划:
基本概念:动态规划是一种算法设计技术,它通过将问题分解为更小的子问题来解决复杂问题,同时保存已解决子问题的结果以避免重复计算。
状态定义:在动态规划中,dp[i][j]通常表示一个状态,这里它表示将长度为i的dna1的前缀转换成长度为j的dna2的前缀所需的最小编辑操作次数。
状态转移方程:这是动态规划的核心,它定义了如何从一个或多个已知状态推导出下一个状态。