You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

从暴力解法推导LCS问题DP子解表的逻辑困惑

从暴力枚举到DP:一步步推导最长公共子序列(LCS)的解法

我完全理解你现在的困惑——从暴力枚举的思路跳转到动态规划确实需要一点「思维转弯」,尤其是如何把指数级的枚举转化为多项式级的子问题求解。咱们从你已经掌握的暴力解法出发,慢慢拆解出DP的核心逻辑。

先明确暴力解法的问题所在

你提到暴力枚举所有子序列的复杂度是O(2^(n+m)),这确实是因为每一个字符都有「选或不选」两种可能,导致子序列数量呈指数级增长。但这里藏着一个关键问题:大量子问题是重复计算的。比如,当你计算P的前i个字符和Q的前j个字符的公共子序列时,会反复用到P前i-1个和Q前j-1个字符的结果——而暴力解法会把这些重复的子问题重新计算一遍,这就是复杂度爆炸的根源。

动态规划的核心就是把这些重复的子问题记录下来,只计算一次,从而把复杂度降到多项式级别。

第一步:定义DP状态(找到子问题)

我们先给子问题一个清晰的定义:

设dp[i][j]表示字符串P的前i个字符(即P[0..i-1],假设字符串从0开始索引)和字符串Q的前j个字符(Q[0..j-1])的最长公共子序列的长度。

为什么这么定义?因为这样可以把原问题(P和Q的LCS)拆解成一系列更小的子问题:比如求dp[n][m](n是P的长度,m是Q的长度),只需要先求出dp[n-1][m-1]、dp[n-1][m]、dp[n][m-1]这些更小的子问题的解。

第二步:推导状态转移方程(子问题之间的关系)

现在我们来分析dp[i][j]和更小的子问题之间的关系,分两种情况:

情况1:P的第i个字符和Q的第j个字符相等(即P[i-1] == Q[j-1])

如果这两个字符相等,那它们一定是P前i个和Q前j个字符的LCS的一部分。因为我们已经找到了一个公共字符,剩下的LCS就是P前i-1个和Q前j-1个字符的LCS,再加上这个字符。所以:

dp[i][j] = dp[i-1][j-1] + 1

举个例子:P是"ABC",Q是"AC",当i=3(对应P的第三个字符'C')、j=2(对应Q的第二个字符'C')时,dp[3][2] = dp[2][1] + 1。而dp[2][1]是"AB"和"A"的LCS长度(1),所以dp[3][2] = 2,正好是"AC"的长度。

情况2:P的第i个字符和Q的第j个字符不相等(即P[i-1] != Q[j-1])

这时候,P前i个和Q前j个字符的LCS,要么是P前i-1个字符和Q前j个字符的LCS,要么是P前i个字符和Q前j-1个字符的LCS——因为当前字符不匹配,我们只能舍弃其中一个字符,取两种情况里的最大值。所以:

dp[i][j] = max(dp[i-1][j], dp[i][j-1])

比如P是"ABD",Q是"ACE",当i=3('D')、j=3('E')时,两个字符不相等,所以dp[3][3] = max(dp[2][3], dp[3][2])。dp[2][3]是"AB"和"ACE"的LCS长度(1),dp[3][2]是"ABD"和"AC"的LCS长度(1),所以结果是1。

第三步:初始化DP表

我们需要先给DP表的「边界」赋值:

  • 当i=0时,P的前0个字符是空字符串,和任何Q的前j个字符的LCS长度都是0,所以dp[0][j] = 0(所有j);
  • 当j=0时,Q的前0个字符是空字符串,和任何P的前i个字符的LCS长度都是0,所以dp[i][0] = 0(所有i)。

这就像盖房子的地基,先把最基础的情况确定下来。

第四步:填充DP表的顺序

因为dp[i][j]依赖于dp[i-1][j-1]、dp[i-1][j]、dp[i][j-1]这三个子问题的解,所以我们需要按从左到右、从上到下的顺序填充整个表格——这样当计算dp[i][j]时,它需要的所有子问题的解都已经计算好了。

举个简单的例子,假设P="ABC",Q="AC",DP表的填充过程如下:

""AC
""000
A011
B011
C012

最终dp[3][2] = 2,就是两个字符串的LCS长度。

为什么这比暴力解法高效?

暴力解法中,每个子问题会被重复计算无数次,而DP表中每个dp[i][j]只计算一次,总共需要计算n*m个状态,时间复杂度直接降到O(n*m),这比指数级的暴力解法高效太多了——这就是动态规划「以空间换时间」的核心思想。

总结一下推导流程

  1. 识别重叠子问题:暴力枚举中反复计算的「P前i个和Q前j个字符的LCS」就是重叠子问题;
  2. 定义DP状态:用dp[i][j]表示上述子问题的解;
  3. 推导转移方程:分字符相等/不相等两种情况,用子问题的解推导当前状态的解;
  4. 初始化边界:处理空字符串的基础情况;
  5. 按顺序填充表格:确保计算当前状态时,所需的子问题解已经存在。

内容的提问来源于stack exchange,提问作者user6679212

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 08:08:15