使用AI编程刷经典DP题【DNA序列编辑距离】的笔记。
1. 问题描述
小R正在研究DNA序列,他需要一个函数来计算将一个受损DNA序列(dna1)转换成一个未受损序列(dna2)所需的最少编辑步骤。编辑步骤包括:增加一个碱基、删除一个碱基或替换一个碱基。
对于两个DNA序列 dna1 和 dna2,求出最少的编辑步骤数,使得 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
3. 解题思路
本问题是典型的 编辑距离问题,即通过最少的操作将一个字符串转换为另一个字符串。常见的动态规划算法可以用于求解。
- 动态规划思路:设
dp[i][j]为将dna1的前i个字符转换为dna2的前j个字符的最少操作数。 - 状态转移:我们有三种操作:
- 删除操作:从
dna1中删除一个字符,即dp[i-1][j] + 1。 - 插入操作:将
dna2[j-1]插入到dna1中,即dp[i][j-1] + 1。 - 替换操作:如果
dna1[i-1]不等于dna2[j-1],进行替换操作,即dp[i-1][j-1] + 1。
- 删除操作:从
通过上述转移方程,我们可以递推出整个 dp 数组,最终得到 dp[n][m],即将 dna1 完全转换为 dna2 所需要的最小操作数。
4. 代码实现
from functools import cache
def solution(dna1, dna2):
n, m = len(dna1), len(dna2)
# 使用缓存来优化递归
@cache
def dfs(i: int, j: int) -> int:
if i < 0:
return j + 1
if j < 0:
return i + 1
# 如果字符相等,直接跳到下一个字符
if dna1[i] == dna2[j]:
return dfs(i - 1, j - 1)
# 否则,进行替换、插入或删除操作
return 1 + min(dfs(i - 1, j), dfs(i, j - 1), dfs(i - 1, j - 1))
# 计算最少编辑步骤
return dfs(n - 1, m - 1)
if __name__ == "__main__":
print(solution("AGCTTAGC", "AGCTAGCT") == 2)
print(solution("AGCCGAGC", "GCTAGCT") == 4)
5. AI练中学评价
- 测试Prompt:
需要一点思路提示。
- 部分结果:
解题思路
好的,让我们来分析一下这个问题。
-
问题理解 你需要计算将一个受损DNA序列(dna1)转换成一个未受损序列(dna2)所需的最少编辑步骤。
-
编辑步骤包括:
- 增加一个碱基
- 删除一个碱基
- 替换一个碱基
-
数据结构选择 由于我们需要计算最少编辑步骤,动态规划(Dynamic Programming, DP)是一个非常适合的算法。我们可以使用一个二维数组 dp 来存储中间结果,其中 dp[i][j] 表示将 dna1 的前 i 个字符转换成 dna2 的前 j 个字符所需的最少编辑步骤。
-
算法步骤
- 初始化: 如果 dna1 为空,那么将 dna1 转换成 dna2 需要 j 次插入操作。 如果 dna2 为空,那么将 dna1 转换成 dna2 需要 i 次删除操作。
- 状态转移: 如果 dna1[i-1] == dna2[j-1],那么 dp[i][j] = dp[i-1][j-1],即不需要任何操作。 否则,dp[i][j] 可以通过以下三种操作中的最小值来得到: 插入:dp[i][j-1] + 1 删除:dp[i-1][j] + 1 替换:dp[i-1][j-1] + 1 最终结果: dp[n][m] 即为将 dna1 转换成 dna2 所需的最少编辑步骤,其中 n 和 m 分别是 dna1 和 dna2 的长度。
- 个人评价:AI的解法就是官方解法,状态转移方程列的很清晰,很早以前当年第一次刷力扣这道题的时候,它还是困难题~到现在AI都可以轻松解决了,还是简单题哈哈哈。Marscode还是很厉害啊。