一,题目详情
1,问题描述
小R正在研究DNA序列,他需要一个函数来计算将一个受损DNA序列(dna1)转换成一个未受损序列(dna2)所需的最少编辑步骤。编辑步骤包括:增加一个碱基、删除一个碱基或替换一个碱基。
2,测试样例
样例1:
输入:dna1 = "AGT",dna2 = "AGCT"
输出:1
样例2:
输入:dna1 = "AACCGGTT",dna2 = "AACCTTGG"
输出:4
样例3:
输入:dna1 = "ACGT",dna2 = "TGC"
输出:3
样例4:
输入:dna1 = "A",dna2 = "T"
输出:1
样例5:
输入:dna1 = "GGGG",dna2 = "TTTT"
输出:4
二,解题思路
1,问题分析
这是一个经典的编辑距离问题,需要找到将一个字符串转换为另一个字符串的最少操作次数。操作包括插入、删除和替换。
2,算法策略
使用动态规划来解决这个问题。动态规划表 dp[i][j] 表示将 dna1 的前 i 个字符转换为 dna2 的前 j 个字符所需的最少操作次数。
具体步骤如下:
- 初始化一个 (m+1) x (n+1) 的动态规划表,其中 m 是 dna1 的长度,n 是 dna2 的长度。
- 填充边界条件:dp[i][0] = i(删除 dna1 的前 i 个字符),dp[0][j] = j(插入 dna2 的前 j 个字符)。
- 对于每个 dp[i][j],如果 dna1[i-1] == dna2[j-1],则 dp[i][j] = dp[i-1][j-1];否则,取替换、删除、插入三种操作的最小值加1。
3,逐步推演(以样例1为例)
输入:dna1 = "AGT", dna2 = "AGCT"
动态规划表的填充过程如下:
| i/j | 0 | 1 | 2 | 3 | 4 |
|---|---|---|---|---|---|
| 0 | 0 | 1 | 2 | 3 | 4 |
| 1 | 1 | 0 | 1 | 2 | 3 |
| 2 | 2 | 1 | 0 | 1 | 2 |
| 3 | 3 | 2 | 1 | 1 | 2 |
最终结果为 dp[3][4] = 1。
三,代码实现
def solution(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 # 插入所有字符
# 填充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:
replace = dp[i-1][j-1] + 1
delete = dp[i-1][j] + 1
insert = dp[i][j-1] + 1
dp[i][j] = min(replace, delete, insert)
return dp[m][n]
1,复杂度分析
-
时间复杂度:O(m*n)
- 遍历整个动态规划表
-
空间复杂度:O(m*n)
- 存储动态规划表
2,边界测试
if __name__ == "__main__":
# 常规测试
print(solution("AGT", "AGCT") == 1) # 样例1
print(solution("AACCGGTT", "AACCTTGG") == 4) # 样例2
print(solution("ACGT", "TGC") == 3) # 样例3
print(solution("A", "T") == 1) # 样例4
print(solution("GGGG", "TTTT") == 4) # 样例5
print(solution("AGCTTAGC", "AGCTAGCT") == 2 )
print(solution("AGCCGAGC", "GCTAGCT") == 4)
四,总结
通过动态规划,我们实现了:
- 最少编辑步骤:找到将一个DNA序列转换为另一个序列的最少操作次数
- 清晰的逻辑:利用动态规划表逐步推导
- 普适性:适用于所有DNA序列
这种解法不仅高效,还易于理解和实现。当遇到“需要计算两个序列之间的转换成本”类问题时,动态规划往往是解决问题的关键策略。