最长公共子序列

article/2025/9/28 10:08:14

最长公共子序列(Longest Common Subsequence,简称 LCS)是一道非常经典的面试题目,因为它的解法是典型的二维动态规划,大部分比较困难的字符串问题都和这个问题一个套路,比如说编辑距离。而且,这个算法稍加改造就可以用于解决其他问题,所以说 LCS 算法是值得掌握的。

题目就是让我们求两个字符串的 LCS 长度:

输入: str1 = "abcde", str2 = "ace" 
输出: 3  
解释: 最长公共子序列是 "ace",它的长度是 3

肯定有读者会问,为啥这个问题就是动态规划来解决呢?因为子序列类型的问题,穷举出所有可能的结果都不容易,而动态规划算法做的就是穷举 + 剪枝,它俩天生一对儿。所以可以说只要涉及子序列问题,十有八九都需要动态规划来解决,往这方面考虑就对了。

下面就来手把手分析一下,这道题目如何用动态规划技巧解决。

一、动态规划思路

第一步,一定要明确 dp 数组的含义。对于两个字符串的动态规划问题,套路是通用的。

比如说对于字符串 s1s2,一般来说都要构造一个这样的 DP table:

为了方便理解此表,我们暂时认为索引是从 1 开始的,待会的代码中只要稍作调整即可。其中,dp[i][j] 的含义是:对于 s1[1..i]s2[1..j],它们的 LCS 长度是 dp[i][j]

比如上图的例子,d[2][4] 的含义就是:对于 "ac""babc",它们的 LCS 长度是 2。我们最终想得到的答案应该是 dp[3][6]

第二步,定义 base case。

我们专门让索引为 0 的行和列表示空串,dp[0][..]dp[..][0] 都应该初始化为 0,这就是 base case。

比如说,按照刚才 dp 数组的定义,dp[0][3]=0 的含义是:对于字符串 """bab",其 LCS 的长度为 0。因为有一个字符串是空串,它们的最长公共子序列的长度显然应该是 0。

第三步,找状态转移方程。

这是动态规划最难的一步,不过好在这种字符串问题的套路都差不多,权且借这道题来聊聊处理这类问题的思路。

状态转移说简单些就是做选择,比如说这个问题,是求 s1s2 的最长公共子序列,不妨称这个子序列为 lcs。那么对于 s1s2 中的每个字符,有什么选择?很简单,两种选择,要么在 lcs 中,要么不在。

这个「在」和「不在」就是选择,关键是,应该如何选择呢?这个需要动点脑筋:如果某个字符应该在 lcs 中,那么这个字符肯定同时存在于 s1s2 中,因为 lcs 是最长公共子序列嘛。所以本题的思路是这样:

用两个指针 ij 从后往前遍历 s1s2,如果 s1[i]==s2[j],那么这个字符一定在 lcs;否则的话,s1[i]s2[j] 这两个字符至少有一个不在 lcs,需要丢弃一个。先看一下递归解法,比较容易理解:

def longestCommonSubsequence(str1, str2) -> int:def dp(i, j):# 空串的 base caseif i == -1 or j == -1:return 0if str1[i] == str2[j]:# 这边找到一个 lcs 的元素,继续往前找return dp(i - 1, j - 1) + 1else:# 谁能让 lcs 最长,就听谁的return max(dp(i-1, j), dp(i, j-1))# i 和 j 初始化为最后一个索引return dp(len(str1)-1, len(str2)-1)

对于第一种情况,找到一个 lcs 中的字符,同时将 i j 向前移动一位,并给 lcs 的长度加一;对于后者,则尝试两种情况,取更大的结果。

其实这段代码就是暴力解法,我们可以通过备忘录或者 DP table 来优化时间复杂度,比如通过前文描述的 DP table 来解决:

def longestCommonSubsequence(str1, str2) -> int:m, n = len(str1), len(str2)# 构建 DP table 和 base casedp = [[0] * (n + 1) for _ in range(m + 1)]# 进行状态转移for i in range(1, m + 1):for j in range(1, n + 1):if str1[i - 1] == str2[j - 1]:# 找到一个 lcs 中的字符dp[i][j] = 1 + dp[i-1][j-1]else:dp[i][j] = max(dp[i-1][j], dp[i][j-1])return dp[-1][-1]

二、疑难解答

对于 s1[i]s2[j] 不相等的情况,至少有一个字符不在 lcs 中,会不会两个字符都不在呢?比如下面这种情况:

所以代码是不是应该考虑这种情况,改成这样:

if str1[i - 1] == str2[j - 1]:# ...
else:dp[i][j] = max(dp[i-1][j], dp[i][j-1],dp[i-1][j-1])

我一开始也有这种怀疑,其实可以这样改,也能得到正确答案,但是多此一举,因为 dp[i-1][j-1] 永远是三者中最小的,max 根本不可能取到它。

原因在于我们对 dp 数组的定义:对于 s1[1..i]s2[1..j],它们的 LCS 长度是 dp[i][j]

这样一看,显然 dp[i-1][j-1] 对应的 lcs 长度不可能比前两种情况大,所以没有必要参与比较。

三、总结

对于两个字符串的动态规划问题,一般来说都是像本文一样定义 DP table,因为这样定义有一个好处,就是容易写出状态转移方程,dp[i][j] 的状态可以通过之前的状态推导出来:

找状态转移方程的方法是,思考每个状态有哪些「选择」,只要我们能用正确的逻辑做出正确的选择,算法就能够正确运行。

如果本文对你有帮助,欢迎关注我的公众号 labuladong,致力于把算法问题讲清楚~

labuladong


http://chatgpt.dhexx.cn/article/UI3Zay8E.shtml

相关文章

最长公共子序列(LCS) 过程图解

1.基本概念 首先需要科普一下,最长公共子序列(longest common sequence)和最长公共子串(longest common substring)不是一回事儿。什么是子序列呢?即一个给定的序列的子序列,就是将给定序列中零…

最长子序列问题详解

提到最长子序列问题,想必大家都不陌生,今天我主要想分享一下我对子序列问题的一些理解: 先拿最长上升子序列问题来说吧: 很明显这是一个动态规化问题,仔细想想也不难得出其状态转移方程 首先介绍一下dp[]数组的含义…

如何查看pip版本

Windows系统如何查看pip版本 直接运行pip show pip

python之pip版本问题

我们在默认安装python软件之后,其自行安装的pip的版本可能比较落后。我们使用pip安装模块的时候经常提示安装失败,查看安装失败原因,部分是因为pip不是最新版本需要升级版本才能继续安装,因此写下一点小心得,让我们都能…

pip 查看可安装版本

pip 查看可安装版本 pip版本: pip -V pip 20.1.1 from /usr/lib/python2.7/site-packages/pip (python 2.7) 查看pip可安装版本 import pip._internal.utils.compatibility_tags print(pip._internal.utils.compatibility_tags.get_supported())

python如何查看pip版本并且升级pip

第一次写Python的学习经历 我之前也安装过Python,今天,终于重新安装了64位的windows的Python,于是在命令行输入:“pip list”出现以下的提示: 这个提示以前也出现过,但是看不懂,也不知道怎么处理,然后又胡…

python pip 查找指定安装包版本信息

一、windows操作系统 pip list | findstr 查找的包(支持模糊查询)例: pip list | findstr huawei结果: 二、linux操作系统 pip list | grep 查找的包(支持模糊查询)

更新pip版本

更新pip版本 从报错来看是因为pip版本为20.2.3,但当前可用版本是21.0.1 python -m ensurepippython -m pip install --upgrade pippip list设置镜像默认源 #临时使用 pip install -i https://pypi.tuna.tsinghua.edu.cn/simple some-package #设置默认源 pip inst…

拉取的docker镜像中更换python以及pip版本

场景: 需要tensorflow1.10 python3.6 的环境,但是拉取的tf1.10镜像中,默认python版本是3.5 ,此时我们需要安装python3.6版本,将python命令的软连接删除,并且指向新安装的3.6,且pip的连接也应该…

Python查看所有版本及pip

1、查看python默认版本: python --version (默认的版本我改为了python3,所以结果是3.7的) 2、查看另外安装的2.7的版本: python2.7 --version 3、查看python安装路径: 1)终端输入python2.7回…

python降低pip版本

场景: 使用Pycharm开发时,经常会遇到因为pip版本过高而导致下载第三方库失败的情况 解决方案: 经查资料,发现可以通过降低pip的版本来解决问题 如下: 第一种方法:使用Terminal命令行进行降级 python -m pip…

Linux查看pip版本号的命令,pip 经常使用命令及控制台怎么查看python 及pip 和已安装包版本号...

在使用python的时候,常常使用到pip这个工具,能够很方便的线上安装依赖库,固然pip还有不少参数均可以帮咱们去查询一些库信息,在安装python的时候,下载带有pip的安装包就能够直接安装pip啦,固然没有带pip的&…

查看pip版本 及升级pip命令报错解决办法

pip install pytest_ordering时警告,pip版本过低 WARNING: You are using pip version 21.1.3; however, version 21.2.4 is available. You should consider upgrading 1、查看pip: pip show pip 2、升级pip:python -m pip install --upg…

最新升级pip命令,查看pip版本命令,pycharm升级pip命令,推荐收藏关注不迷路

1. 查看pip版本命令:pip -V ,可以看看以下代码运行完过后,自己的pip版本有没有升级成功 python升级pip命令:打开命令行输入 python -m pip install -U pip 回车 pycharm升级pip命令 :打开命令行输入: py…

[Python]pip查找包的历史版本

pip查找包的历史版本 场景:在一些时候通过pip install xxx 安装第三方库的时候默认情况下安装最新版本,由于是最新版本有个稳定性就不得不考虑其中,所以部分场景会存在一些bug这就要求我们安装历史版本,对一些更新频率比较高的三…

已解决正确查看pip版本、查看pip帮助命令

已解决(pip查看版本报错)Usage:pip [options] no such option: —verson 文章目录 报错代码报错翻译报错原因解决方法千人全栈VIP答疑群联系博主帮忙解决报错 报错代码 粉丝群一个小伙伴想用pip查看版本报错,但是发生了报错&#…

解决OpenSSL SSL_read: Connection was reset, errno 10054

报错问题: fatal: unable to access ‘https://github.com/Sunyt1992/ref-comet.git/’: OpenSSL SSL_read: Connection was reset, errno 10054 OpenSSL SSL_read:连接已重置,错误号10054 字面意思:服务器的SSL证书灭有经过第三方机构的签署…

TCP 10054

2019独角兽企业重金招聘Python工程师标准>>> TcpSocket:Connection reset by peer 10054 10054 ConnectionReset 客户端网络情况不好的条件下,比如正在下载其他东西,而服务器又有心跳超时处理,客户端的心跳迟迟发不上去&#xff0…

gitHub报错10054、443解决办法

为了学习netty框架,将netty的源码fork到自己的github账号下,使用IDEA进行下载,报了errno 10054的错误,如下所示: 21:40:24.692: [..\..\javaworkspace] git -c credential.helper -c core.quotepathfalse -c log.show…