动态规划Dynamic programming详解-最长公共子序列【python】

简介: 动态规划Dynamic programming详解-最长公共子序列【python】

作者介绍:10年大厂数据\经营分析经验,现任大厂数据部门负责人。

会一些的技术:数据分析、算法、SQL、大数据相关、python

欢迎加入社区:码上找工作

作者专栏每日更新:

LeetCode解锁1000题: 打怪升级之旅

python数据分析可视化:企业实战案例

备注说明:方便大家阅读,统一使用python,带必要注释,公众号 数据分析螺丝钉 一起打怪升级

1. 问题介绍和应用场景

最长公共子序列(LCS)问题是在信息学、生物学以及在日常软件开发中,特别是在版本控制系统的开发中广泛应用的一类问题。LCS可以用来比较两个序列(通常是字符串)的相似度,它寻找在两个序列中都出现过并且所有字符都以相同顺序排列,但不必连续的最长序列。

2. 问题定义和示例

定义: 给定两个字符串 s1s2,找出这两个字符串的最长公共子序列的长度。公共子序列是指在两个字符串中都能找到的序列,其字符顺序与原始字符串中的顺序相同但不必连续。

示例

  • s1 = "abcde"
  • s2 = "ace"

最长公共子序列是 "ace",长度为3。

3. 状态定义

在动态规划中,状态的选择是解题的关键。在本问题中,我们定义 dp[i][j] 为考虑 s1 的前 i 个字符和 s2 的前 j 个字符时的最长公共子序列的长度。

4. 状态转移方程

要构建状态转移方程,考虑以下情况:

  • 如果当前字符 s1[i-1]s2[j-1] 相等,那么这个字符一定在LCS中。因此,dp[i][j] = dp[i-1][j-1] + 1
  • 如果不相等,那么LCS可能是 s1 的前 i-1 个字符和 s2 的前 j 个字符的LCS,或者是 s1 的前 i 个字符和 s2 的前 j-1 个字符的LCS。因此,dp[i][j] = max(dp[i-1][j], dp[i][j-1])

结合上述两点,状态转移方程可以表示为:

5. 初始化和边界情况

初始化对于动态规划问题至关重要,它可以确保状态转移正确进行。对于任何LCS问题:

  • i = 0j = 0 时,即考虑 s1s2 的前0个字符,最长公共子序列长度自然为0。因此,dp[0][j] = 0dp[i][0] = 0 对所有的 ij

6. 算法实现

以下是使用Python语言实现的LCS问题的动态规划算法:

def longest_common_subsequence(s1: str, s2: str) -> int:
    """
    计算两个字符串之间的最长公共子序列的长度。
    :param s1: 第一个字符串
    :param s2: 第二个字符串
    :return: 最长公共子序列的长度
    """
    m, n = len(s1), len(s2)
    # 创建一个二维数组来存储最长公共子序列的长度。
    dp = [[0] * (n + 1) for _ in range(m + 1)]
 
    # 自底向上地构建dp数组
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if s1[i-1] == s2[j-1]:
                dp[i][j] = dp[i-1][j-1] + 1  # 若当前字符相同,则将这一对字符加入到LCS中
            else:
                dp[i][j] = max(dp[i-1][j], dp[i][j-1])  # 否则,考虑不包括s1[i-1]或s2[j-1]的情况
 
    # dp[m][n] 包含了s1[0..m-1]和s2[0..n-1]的最长公共子序列的长度
    return dp[m][n]
 
# 示例使用
s1 = "abcde"
s2 = "ace"
print("最长公共子序列的长度:", longest_common_subsequence(s1, s2))

动态规划逻辑

  • 初始化一个 (m+1) x (n+1) 的二维列表 dp,其中 dp[i][j] 存储 s1i 个字符和 s2j 个字符的最长公共子序列的长度。
  • 遍历字符串 s1s2,通过比较字符确定是否将字符加入到子序列中,或者决定忽略哪个字符以保持子序列尽可能长。

复杂度分析

  • 时间复杂度:O(mn) - 因为需要填充一个 m x n 的矩阵。
  • 空间复杂度:O(mn) - 用于存储每对索引的LCS长度。可以优化到 O(min(m, n)) 通过只保留当前和前一行的状态。

算法图解

图解示例

假设我们有两个字符串:

  • s1 = "abcde"
  • s2 = "ace"

我们需要找到它们的最长公共子序列。

动态规划表初始化

创建一个表格,其中行表示字符串 s1,列表示字符串 s2。我们使用一个 (m+1) x (n+1) 的表格,mn 分别是两个字符串的长度。

初始表格:

      | - | a | c | e |
    -----------------------
    - | 0 | 0 | 0 | 0 |
    a | 0 |   |   |   |
    b | 0 |   |   |   |
    c | 0 |   |   |   |
    d | 0 |   |   |   |
    e | 0 |   |   |   |
填充表格
  • 当我们比较两个字符且它们相同时,我们在左上角的值基础上加一(因为发现了一个新的公共元素)。
  • 当字符不同时,我们取左边和上边值中的最大值。

1.a vs a:

  • dp[1][1]: s1as2a 相同,所以 dp[1][1] = dp[0][0] + 1 = 1.
   | - | a | c | e |
 -----------------------
 - | 0 | 0 | 0 | 0 |
 a | 0 | 1 |   |   |
 b | 0 |   |   |   |
 c | 0 |   |   |   |
 d | 0 |   |   |   |
 e | 0 |   |   |   |

2.a vs c, a vs e:

  • dp[1][2]: 取 dp[1][1]dp[0][2] 的最大值,即 dp[1][2] = max(1, 0) = 1.
  • dp[1][3]: 取 dp[1][2]dp[0][3] 的最大值,即 dp[1][3] = max(1, 0) = 1.

3.b vs a, c, e:

  • 使用与第一行相同的逻辑,dp[2][1] = 1, dp[2][2] = 1, dp[2][3] = 1.

4.c vs a, c, e:

  • dp[3][1] = 1: 使用上一行的结果。
  • dp[3][2] = 2: cc 匹配,dp[3][2] = dp[2][1] + 1 = 2.
  • dp[3][3] = 2: 取 dp[3][2]dp[2][3] 的最大值,即 dp[3][3] = max(2, 1) = 2.

5.d vs a, c, e:

  • 同上,结果与第三行相同。

6.e vs a, c, e:

  • dp[5][1] = 1, dp[5][2] = 2, dp[5][3] = 3: ee 匹配,dp[5][3] = dp[4][2] + 1 = 3.
   | - | a | c | e |
 -----------------------
 - | 0 | 0 | 0 | 0 |
 a | 0 | 1 | 1 | 1 |
 b | 0 | 1 | 1 | 1 |
 c | 0 | 1 | 2 | 2 |
 d | 0 | 1 | 2 | 2 |
 e | 0 | 1 | 2 | 3 |

8. 总结

最长公共子序列问题不仅是一个理论上的问题,而且在实际应用中非常重要,比如在生物信息学中比较DNA序列,在软件工程中比较文件版本等。通过动态规划,我们能够有效地解决这个问题,即使在面对大规模数据时也能保持良好的性能。


欢迎关注微信公众号 数据分析螺丝钉

相关文章
|
4月前
|
算法 开发者 Python
惊呆了!Python算法设计与分析,分治法、贪心、动态规划...这些你都会了吗?不会?那还不快来学!
【7月更文挑战第10天】探索编程巅峰,算法至关重要。Python以其易读性成为学习算法的首选。分治法,如归并排序,将大问题拆解;贪心算法,如找零问题,每步求局部最优;动态规划,如斐波那契数列,利用子问题解。通过示例代码,理解并掌握这些算法,提升编程技能,面对挑战更加从容。动手实践,体验算法的神奇力量吧!
72 8
|
6天前
|
算法 Python
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果
在Python编程中,分治法、贪心算法和动态规划是三种重要的算法。分治法通过将大问题分解为小问题,递归解决后合并结果;贪心算法在每一步选择局部最优解,追求全局最优;动态规划通过保存子问题的解,避免重复计算,确保全局最优。这三种算法各具特色,适用于不同类型的问题,合理选择能显著提升编程效率。
24 2
|
24天前
|
存储 算法 Python
【10月更文挑战第16天】「Mac上学Python 27」小学奥数篇13 - 动态规划入门
本篇将通过 Python 和 Cangjie 双语介绍动态规划的基本概念,并解决一个经典问题:斐波那契数列。学生将学习如何使用动态规划优化递归计算,并掌握编程中的重要算法思想。
92 3
|
4月前
|
算法 Python
Python算法高手进阶指南:分治法、贪心算法、动态规划,掌握它们,算法难题迎刃而解!
【7月更文挑战第10天】探索Python算法的精华:分治法(如归并排序)、贪心策略(如找零钱问题)和动态规划(解复杂问题)。通过示例代码揭示它们如何优化问题解决,提升编程技能。掌握这些策略,攀登技术巅峰。
120 2
|
4月前
|
算法 程序员 Python
算法小白到大神的蜕变之路:Python分治法、贪心、动态规划,一步步带你走向算法巅峰!
【7月更文挑战第9天】探索算法之旅,以Python解锁编程高手之路。分治法如二分查找,将复杂问题拆解;贪心算法解决活动选择,每次选取局部最优;动态规划求斐波那契数列,避免重复计算,实现全局最优。每一步学习,都是编程能力的升华,助你应对复杂挑战,迈向算法大师!
47 1
|
2天前
|
存储 Python
Python编程入门:打造你的第一个程序
【10月更文挑战第39天】在数字时代的浪潮中,掌握编程技能如同掌握了一门新时代的语言。本文将引导你步入Python编程的奇妙世界,从零基础出发,一步步构建你的第一个程序。我们将探索编程的基本概念,通过简单示例理解变量、数据类型和控制结构,最终实现一个简单的猜数字游戏。这不仅是一段代码的旅程,更是逻辑思维和问题解决能力的锻炼之旅。准备好了吗?让我们开始吧!
|
2天前
|
机器学习/深度学习 人工智能 TensorFlow
人工智能浪潮下的自我修养:从Python编程入门到深度学习实践
【10月更文挑战第39天】本文旨在为初学者提供一条清晰的道路,从Python基础语法的掌握到深度学习领域的探索。我们将通过简明扼要的语言和实际代码示例,引导读者逐步构建起对人工智能技术的理解和应用能力。文章不仅涵盖Python编程的基础,还将深入探讨深度学习的核心概念、工具和实战技巧,帮助读者在AI的浪潮中找到自己的位置。
|
3天前
|
设计模式 算法 搜索推荐
Python编程中的设计模式:优雅解决复杂问题的钥匙####
本文将探讨Python编程中几种核心设计模式的应用实例与优势,不涉及具体代码示例,而是聚焦于每种模式背后的设计理念、适用场景及其如何促进代码的可维护性和扩展性。通过理解这些设计模式,开发者可以更加高效地构建软件系统,实现代码复用,提升项目质量。 ####
|
2天前
|
机器学习/深度学习 存储 算法
探索Python编程:从基础到高级应用
【10月更文挑战第38天】本文旨在引导读者从Python的基础知识出发,逐渐深入到高级编程概念。通过简明的语言和实际代码示例,我们将一起探索这门语言的魅力和潜力,理解它如何帮助解决现实问题,并启发我们思考编程在现代社会中的作用和意义。