问题描述
给定两个DNA序列 dna1 和 dna2,需要通过最少的编辑操作将 dna1 转换为 dna2。可用的编辑操作有三种:
- 插入一个碱基(Insert):在
dna1中的某个位置插入一个碱基。 - 删除一个碱基(Delete):删除
dna1中的某个碱基。 - 替换一个碱基(Replace):将
dna1中的某个碱基替换为dna2中对应位置的碱基。
目标是计算最少的编辑步骤数。
解题思路
这道题实际上是 编辑距离(Levenshtein Distance) 问题的变体。可以使用动态规划(Dynamic Programming,简称 DP)来解决。编辑距离的核心思想是,通过逐步比较两个字符串(这里是DNA序列),计算从一个字符串转换为另一个字符串所需的最小操作数。
具体步骤如下:
- 定义状态:
设dp[i][j]表示将dna1的前i个字符转换为dna2的前j个字符所需的最少编辑步骤。 - 初始化边界条件:
dp[i][0]表示将dna1的前i个字符转换为空字符串,需要i次删除操作。dp[0][j]表示将空字符串转换为dna2的前j个字符,需要j次插入操作。
- 状态转移:
- 如果
dna1[i-1] == dna2[j-1],则不需要进行任何操作,dp[i][j] = dp[i-1][j-1]。 - 如果
dna1[i-1] != dna2[j-1],则有三种可能的操作:- 删除:删除
dna1[i-1],即dp[i][j] = dp[i-1][j] + 1。 - 插入:在
dna1[i-1]后插入dna2[j-1],即dp[i][j] = dp[i][j-1] + 1。 - 替换:将
dna1[i-1]替换为dna2[j-1],即dp[i][j] = dp[i-1][j-1] + 1。
- 删除:删除
- 取这三者中的最小值作为
dp[i][j]。
- 如果
- 目标结果:
最终的答案就是dp[m][n],其中m和n分别是dna1和dna2的长度。
动态规划实现
def solution(dna1, dna2):
m, n = len(dna1), len(dna2)
# 初始化 dp 数组,大小为 (m+1) x (n+1)
dp = [[0] * (n + 1) for _ in range(m + 1)]
# 初始化边界条件
for i in range(m + 1):
dp[i][0] = i # 将 dna1 转为空字符串的操作数
for j in range(n + 1):
dp[0][j] = j # 将空字符串转为 dna2 的操作数
# 动态规划填充 dp 数组
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]
# 测试案例
if __name__ == "__main__":
# 测试案例
print(solution("AGCTTAGC", "AGCTAGCT") == 2) # 应输出 True
print(solution("AGCCGAGC", "GCTAGCT") == 4) # 应输出 True
print(solution("AGT", "AGCT") == 1) # 应输出 True
print(solution("AACCGGTT", "AACCTTGG") == 4) # 应输出 True
print(solution("ACGT", "TGC") == 3) # 应输出 True
print(solution("A", "T") == 1) # 应输出 True
print(solution("GGGG", "TTTT") == 4) # 应输出 True
复杂度分析
- 时间复杂度:
O(m * n),其中m和n分别是dna1和dna2的长度。因为我们需要填充一个大小为(m+1) x (n+1)的 DP 数组,并且每个位置的计算都需要常数时间。 - 空间复杂度:
O(m * n),我们需要一个大小为(m+1) x (n+1)的二维数组来存储中间结果。
总结
通过使用动态规划解决DNA序列的编辑问题,可以有效地计算出从一个DNA序列转换到另一个序列所需的最小编辑步骤。该方法具有较高的时间和空间复杂度,但适用于中等规模的输入。在实际应用中,我们可以通过改进空间复杂度(例如使用滚动数组)来优化内存使用。