动态规划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序列,在软件工程中比较文件版本等。通过动态规划,我们能够有效地解决这个问题,即使在面对大规模数据时也能保持良好的性能。


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

相关文章
|
20天前
|
算法 数据挖掘 开发者
LeetCode题目55:跳跃游戏【python5种算法贪心/回溯/动态规划/优化贪心/索引哈希映射 详解】
LeetCode题目55:跳跃游戏【python5种算法贪心/回溯/动态规划/优化贪心/索引哈希映射 详解】
|
20天前
|
SQL 算法 数据可视化
python 贪心算法 动态规划实现 跳跃游戏ll【力扣题45】
python 贪心算法 动态规划实现 跳跃游戏ll【力扣题45】
|
20天前
|
存储 SQL 算法
【动态规划】切割钢条详解python
【动态规划】切割钢条详解python
|
20天前
|
存储 SQL 算法
动态规划Dynamic programming详解-背包问题【python】
动态规划Dynamic programming详解-背包问题【python】
|
存储 算法 决策智能
Python算法题解:动态规划解0-1背包问题
Python算法题解:动态规划解0-1背包问题
|
7天前
|
机器学习/深度学习 人工智能 前端开发
Python中的模块化编程
【6月更文挑战第17天】Python模块化编程与软件架构设计的关键在于拆分任务到独立模块,提高代码的可维护性、可重用性和可扩展性。例如,学生管理系统可分解为录入、查询和删除模块。MVC和MVVM架构模式有助于组织代码,而微服务和函数式编程将在未来发展中扮演重要角色。通过示例代码,读者能学习如何实现这些概念,提升项目开发效率和质量。
155 57
|
14天前
|
测试技术 虚拟化 云计算
GitHub高赞!速通Python编程基础手册,被玩出花了!
随着云时代的来临,Python 语言越来越被程序开发人员喜欢和使用,因为其不仅简单易学,而且还有丰富的第三方程序库和相应完善的管理工具。 从命令行脚本程序到 GUI程序,从图形技术到科学计算,从软件开发到自动化测试,从云计算到虚拟化,所有这些领域都有 Python 的身影。 今天给小伙伴们分享的这份手册采用以任务为导向的编写模式,全面地介绍了 Python 编程基础及其相关知识的应用,讲解了如何利用 Python 的知识解决部分实际问题。
GitHub高赞!速通Python编程基础手册,被玩出花了!
|
4天前
|
数据挖掘 数据处理 Python
Python编程入门:从基础到实践
【6月更文挑战第26天】这篇文章引导读者逐步学习Python编程,从基础语法如变量、数据类型(整数、浮点数、字符串)到条件语句、循环(if/for/while),再到函数定义和模块导入。通过实例展示了Python在文本处理、数据分析(使用pandas)和Web开发(使用Flask)的应用。学习Python能为初学者开启更广阔的技术领域,如面向对象编程、并发和网络编程等。
|
2天前
|
设计模式 程序员 测试技术
老程序员分享:Python数据模型及Pythonic编程
老程序员分享:Python数据模型及Pythonic编程
10 1
|
5天前
|
Python
Python多进程编程详细剖析
Python多进程编程详细剖析
14 3