LC300 最长递增子序列¶
中等通过率 58.9%python3核心代码模式动态规划样例通过牛客 AC数组二分查找动态规划最长上升子序列
讲解章节:线性 DP
一句话
维护「每种长度的最小结尾」,二分找位置替换,表长就是答案。
解题思路¶
这题考什么¶
LIS(Longest Increasing Subsequence,最长上升子序列)的两种标准解法, 见 线性 DP。
写法一(O(n 的平方) 的朴素 DP),思路最直白:
状态定义:f[i] = 以 nums[i] 结尾的最长严格递增子序列长度。
转移方程:
f[i] = 1 + max(f[j]) 对所有 j < i 且 nums[j] < nums[i]
没有合法的 j 时 f[i] = 1(自己单独成序列)。答案是所有 f[i] 的最大值。
必须钉死「以 nums[i] 结尾」,否则上一格的信息无法与 nums[i] 拼接。
写法二(本文件采用的 O(n log n) 贪心加二分),回应题面的进阶要求。
换一个状态:
tails[k] = 所有长度为 k+1 的递增子序列当中,结尾元素的最小值
贪心的道理:同样长度的子序列,结尾越小越容易被后面的元素接上,
所以只保留最小的那个结尾,其余可以安全丢弃。
tails 一定是**严格递增**的:若长度 k+1 的最小结尾不小于长度 k+2 的最小结尾,
把后者截去最后一位就得到一个长度 k+1 而结尾更小的子序列,与「最小」矛盾。
既然有序,就能二分。
处理 x 时,用二分找出第一个大于等于 x 的位置 i:
i 等于表长 -> x 比所有结尾都大,能接在最长的那条后面,表尾追加,LIS 长度加一
否则 -> tails[i] = x,长度不变但结尾更小,为后续留出更多余地
**tails 不是答案序列本身**,它只是每种长度的「最优结尾」快照。
中间的替换会让不同时期的元素混进同一张表,表里元素在原数组中的先后顺序
因此得不到保证:nums = [3, 4, 1] 跑完后 tails = [1, 4],
而 1 出现在 4 的**后面**,[1, 4] 根本不是 nums 的子序列。
表长永远是对的,表的内容只能当中间量看;要还原真正的序列必须另记前驱下标。
数据规模与复杂度¶
1 <= nums.length <= 2500,-1e4 <= nums[i] <= 1e4。 朴素 DP 是 2500 的平方 / 2 约 3e6 次内层迭代,本机实测约 0.22 秒, 对这个数据范围本已够用;采用二分版是为了满足题面进阶要求的 O(n log n)。 时间 O(n log n) = 2500 * 12 约 3e4 次比较,空间 O(n)(tails 最长与 nums 等长)。
坑在哪¶
- 必须用 bisect_left 而不是 bisect_right。题目要求严格递增, 遇到与某个结尾相等的 x 时,bisect_left 会把那个结尾顶掉(长度不增), bisect_right 则会跳到它右边,把相等的元素当成「更大」接上去。 nums = [7,7,7,7,7,7,7] 用 bisect_right 会返回 7,正确答案是 1。
- 判断条件是 i == len(tails) 而不是 i > len(tails)。 bisect_left 的返回值范围是 0 到表长(含),等于表长就表示「插在末尾」。
- 追加与替换是互斥的两条路,替换时不能同时增加长度。 写成无条件 append 会让 tails 变成 nums 的副本,答案退化成 n。
- 答案是 len(tails),不是 tails 里的某个值,也不是 max(tails)。 表长才是长度这一维的最大值。
- 值域含负数(-1e4 起),不能用 0 当哨兵初值来简化二分。 从空表起手是最省事也最安全的写法。
样例复核¶
nums = [10, 9, 2, 5, 3, 7, 101, 18]。
10 -> tails = [10] (空表,追加)
9 -> 找到位置 0,替换 -> [9]
2 -> 位置 0,替换 -> [2]
5 -> 位置 1 = 表长,追加 -> [2, 5]
3 -> 位置 1(5 是第一个 >= 3),替换 -> [2, 3]
7 -> 位置 2 = 表长,追加 -> [2, 3, 7]
101 -> 位置 3 = 表长,追加 -> [2, 3, 7, 101]
18 -> 位置 3(101 是第一个 >= 18),替换 -> [2, 3, 7, 18]
返回 4,与期望输出一致。注意最后一步:长度没变,但把结尾从 101 换成 18, 使得后面若再来一个 20 仍能接上——这正是「结尾越小越好」的价值。
参考实现¶
[:octicons-arrow-left-16: LC295](LC295.md) [LC322 :octicons-arrow-right-16:](LC322.md)