PrepStack · Markdown HTML

子序列 DP

动态规划 · HTML

数学 / 逻辑形态

单序列:dp[i]=以 i 结尾的最优子序列,枚举 j<i 且 compatible(j,i)。双序列:dp[i][j]=前缀 A[:i],B[:j] 的答案,相等时匹配,否则跳过一侧/执行编辑。

识别信号

容易误判的相似信号

SignalWhy it is different
连续子数组/子串连续性通常用滑动窗口、前缀和或局部 DP,不应允许随意跳过。
只需判断模式是否为子序列一次查询可双指针线性完成,无需二维 DP。
集合匹配无顺序若相对顺序不重要,哈希/计数或二分图匹配可能更合适。

核心不变量

每个已计算状态严格对应其末端/前缀定义,并已考虑所有合法的最后匹配、跳过或编辑动作。

状态、转移与处理顺序

ItemDefinition
State单序列末端 i,或两个前缀长度 (i,j);可附 parent/action 恢复具体序列。
Transition从最后动作反推:匹配两末项、跳过 A 末项、跳过 B 末项,或执行替换/插入/删除并聚合。
Frontier / order按增大 i(以及 j)的 DAG 顺序填表;二维状态可按行滚动,但恢复路径通常需完整信息。

Python 骨架

def lcs_length(a, b):
    prev = [0] * (len(b) + 1)
    for x in a:
        cur = [0]
        for j, y in enumerate(b, 1):
            if x == y:
                cur.append(prev[j - 1] + 1)
            else:
                cur.append(max(prev[j], cur[-1]))
        prev = cur
    return prev[-1]

正确性思路

  1. 空前缀与任何序列的公共子序列长为 0。
  2. 末项相等时存在最优解匹配它们并加到更短前缀;不等时任一最优解至少跳过一个末项。
  3. 转移覆盖这些互斥最后动作,按前缀长度归纳得到正确答案。

复杂度

TimeSpaceParameters
典型双序列 O(mn);单序列朴素 O(n²)长度可滚动到 O(min(m,n));恢复路径常 O(mn)m/n 为序列长度。

边界条件

错误模式

PatternWhy it fails / fix
子序列与子串混淆子序列可跳过;状态和转移必须允许不连续。
相等时仍漏算跳过分支需根据具体目标证明匹配末项安全;泛化代价下可能仍要比较其他动作。
滚动数组引用同一列表prev/cur 必须代表不同层,避免本行污染上一行。
只存长度却要求恢复需保存 parent、完整表或用额外分治重构。

选择与排除规则

相关模板

RelationTemplateBoundary
specialization线性 DP / 一维状态递推状态表示以某位置结尾或两个前缀。

原创微型例子

环节内容
Result长度为 2,可取 'cb' 或 'ab'。
SetupA='cab',B='acb',求公共子序列长度。
Trace末尾 b 相同,可从前缀 'ca' 与 'ac' 的答案 1 加一。

Recall prompts

我的补充