古生物DNA序列血缘分析——最小编辑距离问题
在古生物学的研究中,分析不同物种之间的血缘关系通常需要比较它们的DNA序列。DNA序列由四种核苷酸(A、C、G、T)组成,而不同物种的DNA序列虽然有相似之处,但通常也存在差异。为了衡量这些差异并分析物种之间的血缘关系,我们可以通过计算两条DNA序列之间的“最小变异次数”来实现。
在这篇博客中,我们将解析如何通过最小编辑距离算法来解决这一问题。这个问题的核心是通过三种基本操作(插入、删除、替换)来将一条DNA序列转化为另一条DNA序列。最小编辑距离即为这三种操作所需的最少次数。
说人话
给定两条DNA序列dna1和dna2,我们需要计算将dna1转化为dna2所需的最少操作次数。可以进行三种操作:
- 插入操作:在
dna1中插入一个字符; - 删除操作:从
dna1中删除一个字符; - 替换操作:将
dna1中的一个字符替换为dna2中的字符。
这三种操作的目标是使得dna1和dna2完全相同。
测试用例
-
样例1:
输入:dna1 = "AGT",dna2 = "AGCT"
输出:1
解释:只需在dna1的末尾插入一个字符C,就可以使两条序列相同。 -
样例2:
输入:dna1 = "AACCGGTT",dna2 = "AACCTTGG"
输出:4
解释:需要进行四次操作,具体步骤可以是替换、删除、插入等。 -
样例3:
输入:dna1 = "ACGT",dna2 = "TGC"
输出:3
解释:需要删除A,替换C为G,插入T。 -
样例4:
输入:dna1 = "A",dna2 = "T"
输出:1
解释:只需替换A为T。 -
样例5:
输入:dna1 = "GGGG",dna2 = "TTTT"
输出:4
解释:需要替换四次G为T。
思路解析
dp数组的含义
这个问题是经典的“编辑距离”问题,通常通过动态规划来求解。我们可以用一个二维数组dp来表示状态,其中dp[i][j]表示将dna1的前i个字符转换为dna2的前j个字符所需的最少操作次数。
动态规划状态转移
-
初始化:
dp[0][0] = 0,表示两条空字符串之间的编辑距离为0。dp[i][0] = i,表示将dna1的前i个字符转为空字符串,需要进行i次删除操作。dp[0][j] = j,表示将空字符串转为dna2的前j个字符,需要进行j次插入操作。
-
状态转移:
- 如果
dna1[i-1] == dna2[j-1],那么dp[i][j] = dp[i-1][j-1],即如果当前字符相同,则不需要做任何操作,继承上一步的最小操作数。 - 否则,
dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1,这代表了三种操作:- 替换操作:
dp[i-1][j-1] + 1。 - 删除操作:
dp[i-1][j] + 1。 - 插入操作:
dp[i][j-1] + 1。
- 替换操作:
- 如果
最终,我们的答案就存储在dp[m][n]中,其中m和n分别是dna1和dna2的长度。
代码详解
def solution(dna1, dna2):
# 获取两个DNA序列的长度
m = len(dna1)
n = len(dna2)
# dp[i][j] 表示将dna1的前i个字符转换为dna2的前j个字符所需的最小操作次数
dp = [[0] * (n + 1) for _ in range(m + 1)]
# 初始化第一行和第一列
# 第一行:将空字符串转换为dna2的前j个字符需要j次添加操作
for j in range(n + 1):
dp[0][j] = j
# 第一列:将dna1的前i个字符转换为空字符串需要i次删除操作
for i in range(m + 1):
dp[i][0] = i
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-1], dp[i-1][j], dp[i][j-1]) + 1
# 返回最终结果
return dp[m][n]
代码解读
-
初始化动态规划表:
- 创建一个
(m+1) x (n+1)的二维列表dp,表示从dna1的前i个字符到dna2的前j个字符的最小操作数。
- 创建一个
-
边界条件的设置:
dp[0][j]表示将空字符串转为dna2的前j个字符,只能通过插入操作来完成。dp[i][0]表示将dna1的前i个字符转为空字符串,只能通过删除操作来完成。
-
填充动态规划表:
- 对于每一对字符
dna1[i-1]和dna2[j-1],我们考虑三种可能的操作,并选择其中的最小操作次数。
- 对于每一对字符
-
最终答案:
dp[m][n]即为dna1转换为dna2的最小操作次数。
时间复杂度
- 时间复杂度:
O(m * n),其中m和n分别是dna1和dna2的长度。我们需要填充一个(m+1) x (n+1)的二维表。 - 空间复杂度:
O(m * n),我们需要一个大小为(m+1) x (n+1)的二维数组来存储中间结果。