线性 DP 总结与最长递增子序列(LIS)与二维扩展

基础题目:300. 最长递增子序列
扩展题目:354. 俄罗斯套娃信封问题


一、引言:LIS 在「线性 DP」中的位置

在完整学习了:

  • 打家劫舍(选 / 不选)
  • 爬楼梯(排列计数)
  • 背包问题(容量约束)
  • 双序列线性 DP(公共子序列)

之后,最长递增子序列(LIS)是线性 DP 中最后、也是最容易“升维”的一种模型

它的核心特征非常明确:

单序列 + 子序列 + 有序性约束

一旦理解 LIS,不仅可以解决一维问题,还可以通过排序 + 降维,解决看似更复杂的二维、分组问题。


二、354. 俄罗斯套娃信封问题

问题本质与第一直觉

每个信封用 (w, h) 表示,若:

1
w1 < w2 且 h1 < h2

则信封 1 可以嵌套进信封 2。

直觉思路是:

把每个信封当成一个“元素”,求最长递增子序列

但这里存在一个关键陷阱

题目允许任意重排信封顺序
→ 原数组顺序是无意义的
→ 直接在二维数组上做 LIS 会漏解


正确建模:二维 → 一维的关键转化

要想使用 LIS,必须满足:

只在一个维度上递增

因此需要先消除一个维度的干扰。

核心转化步骤

Step 1:排序

  • 宽度 w升序
  • 高度 h降序(当 w 相同)
1
2
3
4
5
6
7
// 双关键字排序:宽度升序,高度降序
Arrays.sort(envelopes, (a, b) -> {
if (a[0] == b[0]) {
return b[1] - a[1]; // 高度降序
}
return a[0] - b[0]; // 宽度升序
});

为什么 w 相同时要按 h 降序?

这是本题最容易被忽略、也是面试最常考的点

如果:

  • w 相同
  • h 也按升序排

那么在之后对 h 求 LIS 时,会错误地把同一宽度的信封选进子序列,违反题意(宽度必须严格递增)。

h 降序 的作用是:

在 LIS(严格递增)中,相同 w 的信封不可能同时被选中


问题彻底降维:只看高度 h

排序完成后:

  • 宽度已经隐式满足递增约束
  • 只需要在高度数组上求 严格递增 LIS

问题就完全转化为:

300. 最长递增子序列


算法复杂度分析

  • 排序:O(n log n)
  • LIS(贪心 + 二分):O(n log n)

整体复杂度:

1
O(n log n)

满足 n ≤ 10^5 的数据规模要求。

三、心得体会(俄罗斯套娃 → LIS)

这道题真正考察的不是 LIS 本身,而是问题转化能力

  1. 是否意识到原顺序无意义
  2. 是否能通过排序消掉一个维度
  3. 是否处理了“相等元素”的边界情况

本质模型可以总结为一句话:

二维严格递增问题 = 排序 + 一维 LIS


四、线性 DP 模型全景总结

回顾整个线性 DP 学习路径,可以清晰地分为 五大模型


打家劫舍模型(选 / 不选 + 相邻约束)

核心特征

  • 枚举元素选或不选
  • 相邻元素不能同时选

典型变形

  • 打家劫舍 II(成环)
    → 拆成「选第一个 / 不选第一个」
  • 删除并获得点数
    → 构造值域数组,转化为打家劫舍

爬楼梯模型(排列型 DP)

核心特征

  • 顺序不同 = 不同方案
  • 每一步选择不同“动作”

常见变形

  • 最小代价爬楼梯
  • 目标字符串构造方案数
  • 组合总和(顺序相关)
  • 分组 + 爬楼梯

背包模型(容量约束)

分类

  • 01 背包(不能重复选)
  • 完全背包(可以重复选)

常见问法

  • 至多 / 恰好 / 至少装满
  • 求最大值 / 最小值 / 方案数

重要收获

  • 空间优化的本质:

    明确当前状态依赖哪些旧状态

    是否会发生状态覆盖

    必要时引入 pre


双序列线性 DP

核心特征

  • 操作两个序列
  • 关注“公共部分”

典型问题

  • 最长公共子序列(LCS)
  • 最短公共超序列(SCS)
  • 编辑距离

最长递增子序列(LIS)

核心特征

  • 单序列
  • 子序列
  • 有序性约束(递增 / 非递减)

两种解法层级

解法 时间复杂度 适用
经典 DP O(n²) 理解模型
贪心 + 二分 O(n log n) 大规模数据

关键技巧

交换 dp 的“状态”与“状态值”

  • g[k] 表示:
    • 长度为 k+1 的递增子序列
    • 其末尾元素的最小值

严格来说:

  • g 不存在状态覆盖
  • 已不再是传统 DP
  • 而是 贪心 + 有序结构 + 二分查找

相关代码

本文涉及的所有代码与笔记,均已同步至我的 GitHub 算法仓库,作为 Java 后端校招过程中的学习记录。