问题描述
小R正在研究DNA序列,他需要一个函数来计算将一个受损DNA序列(dna1)转换成一个未受损序列(dna2)所需的最少编辑步骤。编辑步骤包括:增加一个碱基、删除一个碱基或替换一个碱基。
测试样例
样例1:
输入:
dna1 = "AGT",dna2 = "AGCT"
输出:1
样例2:
输入:
dna1 = "AACCGGTT",dna2 = "AACCTTGG"
输出:4
样例3:
输入:
dna1 = "ACGT",dna2 = "TGC"
输出:3
题目解析:
此问题本质是一个经典的编辑距离问题。
定义一个二维数组 dp,其中 dp[i][j] 表示将 dna1 的前 i 个字符转换为 dna2 的前 j 个字符所需的最少编辑步骤。
对于每个位置,可以选择:
- 增加:使得 dna1 增加一个字符。
- 删除:使得 dna1 删除一个字符。
- 替换:替换掉当前的字符。
思路分析:
动态规划状态转移
- 边界条件:
- 如果 dna1 为空,只能通过增加操作将其变为 dna2,所需步数为 len(dna2)。
- 如果 dna2 为空,只能通过删除操作将 dna1 清空,所需步数为 len(dna1)。
- 状态转移方程:
- 如果 dna1[i-1] == dna2[j-1](当前字符相等):
dp[i][j] = dp[i-1][j-1] - 如果 dna1[i-1] != dna2[j-1](当前字符不相等):
dp[i][j] = min(dp[i-1][j-1] + 1, dp[i-1][j] + 1, dp[i][j-1] + 1)
其中:
dp[i-1][j-1] + 1表示替换。
dp[i-1][j] + 1表示删除。
dp[i][j-1] + 1表示增加。
解决方案:
代码实现:
def solution(dna1, dna2):
# 获取两段 DNA 序列的长度
n, m = len(dna1), len(dna2)
# 初始化 DP 表
dp = [[0] * (m + 1) for _ in range(n + 1)]
# 边界条件
for i in range(n + 1):
dp[i][0] = i # 删除操作
for j in range(m + 1):
dp[0][j] = j # 增加操作
# 状态转移
for i in range(1, n + 1):
for j in range(1, m + 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] + 1, # 替换
dp[i - 1][j] + 1, # 删除
dp[i][j - 1] + 1 # 增加
)
# 返回最终结果
return dp[n][m]
代码解释:
-
输入处理:
- 通过
len(dna1)和len(dna2)获取 DNA 序列长度。 - 初始化一个大小为
(n+1) x (m+1)的二维数组dp,存储每个子问题的结果。
- 通过
-
边界初始化:
dp[i][0] = i表示将dna1的前i个字符清空所需的删除次数。dp[0][j] = j表示将空串扩展为dna2的前j个字符所需的增加次数。
-
动态规划计算:
- 遍历所有可能的子问题,通过状态转移方程更新
dp[i][j]。 - 确保选择替换、增加或删除中的最小值。
- 遍历所有可能的子问题,通过状态转移方程更新
-
返回结果:
dp[n][m]即为将整个dna1转换为dna2所需的最少编辑步骤。
示例测试:
-
样例1:
print(solution("AGCCGAGC", "GCTAGCT")) # 输出 4 -
样例2:
print(solution("AGCTTAGC", "AGCTAGCT"))# 输出 2
时间复杂度:
- 状态转移需要遍历每对字符组合:,其中和是两个 DNA 序列的长度。
空间复杂度:
- 存储二维数组:。
总结
该算法通过动态规划解决了编辑距离问题:
-
优势:时间和空间复杂度适中,适合处理中等规模的 DNA 序列。
-
扩展性:可以通过压缩 DP 表降维,进一步优化空间复杂度。