刷题笔记1-DNA序列编辑距离

103 阅读5分钟

题目解析

在进行编程题目的练习时,我们经常遇到涉及字符串、动态规划和算法优化的问题。最近,我在使用豆包MarsCode AI刷题时,遇到了一个经典的编辑距离问题,也被称作Levenshtein距离问题,题目要求计算将一个DNA序列(dna1)转换为另一个DNA序列(dna2)所需要的最少编辑步骤。

思路解析

编辑距离是一个经典的动态规划问题。其定义是计算将一个字符串转换成另一个字符串所需要的最少编辑步骤,允许的操作包括:插入字符删除字符替换字符

状态定义

  • dp[i][j]表示将dna1的前i个字符转化为dna2的前j个字符所需要的最小编辑步骤。

递推公式

  • 如果dna1[i-1] == dna2[j-1],则dp[i][j] = dp[i-1][j-1],因为两个字符相同,不需要额外的操作。

  • 如果dna1[i-1] != dna2[j-1],则dp[i][j]为以下三者中的最小值:

    • 删除操作:dp[i-1][j] + 1
    • 插入操作:dp[i][j-1] + 1
    • 替换操作:dp[i-1][j-1] + 1

初始化

  • 如果dna1的前i个字符转换成空字符串需要i步操作,即dp[i][0] = i
  • 如果空字符串转换成dna2的前j个字符需要j步操作,即dp[0][j] = j

图解

假设我们有两个DNA序列:

  • dna1 = "AGT"
  • dna2 = "AGCT"

我们需要通过编辑操作将dna1转换成dna2,通过如下的动态规划表格计算最少编辑步骤:

AGCT
0123
A1012
G2112
T3221
  • dp[0][0] = 0:两个空字符串相等。
  • dp[1][0] = 1:从空字符串插入字符'A'。
  • dp[1][1] = 0:字符'A'与'A'匹配,无操作。
  • dp[1][2] = 1:字符'A'替换为'G',需要1次替换。
  • dp[1][3] = 2:字符'A'替换为'G',然后插入'C',共两步。
  • ...

最终,dp[3][4] = 1,即将AGT转换为AGCT需要1次插入操作。

代码详解

python
def solution(dna1, dna2):
    len1, len2 = len(dna1), len(dna2)
    
    # 创建一个二维dp数组,dp[i][j]表示dna1前i个字符转换成dna2前j个字符的最小编辑距离
    dp = [[0] * (len2 + 1) for _ in range(len1 + 1)]
    
    # 初始化第一列和第一行
    for i in range(len1 + 1):
        dp[i][0] = i  # 删除操作
    for j in range(len2 + 1):
        dp[0][j] = j  # 插入操作
    
    # 填充dp表
    for i in range(1, len1 + 1):
        for j in range(1, len2 + 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][j - 1] + 1,    # 插入一个字符
                               dp[i - 1][j - 1] + 1)  # 替换一个字符
    
    return dp[len1][len2]

# 测试
if __name__ == "__main__":
    print(solution("AGCTTAGC", "AGCTAGCT") == 2)
    print(solution("AGCCGAGC", "GCTAGCT") == 4)

该代码通过创建一个二维数组dp来存储每一步的结果,然后利用动态规划的状态转移方程来计算最终的最小编辑步骤。

知识总结

通过这道题,我加深了对动态规划的理解,特别是如何在实际问题中应用动态规划来求解字符串的最小编辑距离。在这个过程中,我学习到了以下几个知识点:

  1. 动态规划的二维数组结构:在很多类似的最优化问题中,使用二维数组存储子问题的结果,可以避免重复计算,极大地提高效率。
  2. 状态转移方程的设计:明确问题中的操作(如插入、删除、替换)与状态之间的关系,是设计动态规划解法的关键。
  3. 初始化的重要性:合理的初始化可以帮助简化边界情况的处理,使得算法更加简洁高效。

学习计划

在使用豆包MarsCode AI刷题的过程中,我总结出了一个高效的学习计划:

  1. 分阶段刷题:根据题目的难度将题目分为初级、中级、高级三个阶段。初级题目可以帮助你掌握基础的算法和数据结构,中级题目可以锻炼你的思维深度和解题技巧,高级题目则有助于提升问题解决的综合能力。
  2. 错题回顾:每当错题出现时,不仅要看题解,更要尝试理解为何自己会出错。通过改正错题和反思过程,可以避免同样的错误重复出现。
  3. 定期复习:定期回顾已做过的题目,特别是已经掌握的题型和知识点,确保不遗忘,保持技能的流畅性。

工具运用

将豆包MarsCode AI的刷题功能与其他学习资源结合,能够达到更好的学习效果。具体方法包括:

  1. 结合刷题与资料学习:在遇到难题时,可以查阅相关资料,理解算法的原理,再来尝试解决问题。豆包MarsCode AI提供了丰富的题解与讨论区,可以和其他学习者交流经验,快速找到解决方案。
  2. 通过AI总结学习:利用AI智能反馈,对每次的解答和解题思路进行总结和优化,找出自己的薄弱点,进行有针对性的强化训练。

通过以上方法,能够更好地提升编程能力,并且在未来的工作中能够游刃有余地解决各种算法问题。

总结

豆包MarsCode AI为编程学习者提供了一个非常强大的平台,它不仅通过题库来进行算法训练,还能根据学习进度和错误情况智能推荐适合的题目,从而帮助学习者在较短的时间内掌握编程和算法的精髓。合理制定学习计划,结合AI工具和其他学习资源,会让学习效果事半功倍。