LeetCode 300. 最长递增子序列
题目描述

题意分析
输入一个整数数组,要求返回其中最长严格递增子序列的长度。子序列由原数组中若干个位置组成,这些位置的下标必须递增,但不要求相邻;被跳过的元素完全不参与。
两个词各自带着约束。「子序列」意味着每个位置都是独立的选或不选,共有指数多种组合,但相对顺序不能打乱——因此不能先排序再处理,排序会凭空造出原数组里不存在的顺序。「严格递增」意味着相等的元素不能同时出现在一条序列里,所有比较都必须用
<而不是<=,这一条只在数组含重复值时才暴露出来。题目只问长度,不要求给出具体是哪一条子序列。这个信息量的差别很关键:只要长度的话,中间过程可以丢掉大量细节;一旦要求还原序列本身,就必须额外记录每个元素的前驱。
另外题面的进阶明确问「能否把时间复杂度降到 $O(n \log n)$」。这句话透露了两个信号:一是平方级的做法在出题人预期之内,可以放心先拿下;二是 $n \log n$ 这个形状意味着每个元素只能分摊到对数级的工作量,「对每个元素回头看一遍所有前面的元素」这种模式注定要被替换掉。
需要留意的边界:数组只有一个元素时答案是 1,因为单个元素本身就是长度 1 的递增子序列;数组整体递减(例如
[5,4,3,2,1])时答案也是 1,所以答案的下界是 1 而不是 0;数组元素全部相等时答案同样是 1,这是检验「严格」条件有没有写对的最短用例。
解法:贪心 + 二分查找
核心思路
用
tails[i]记录长度为i + 1的递增子序列中,最小的结尾值。tails严格递增,因此遍历每个num时,可以二分找到第一个大于等于它的位置:
- 找不到时,将
num追加到末尾,最长长度加一。- 找到时,用
num替换该位置,让同长度的序列更容易继续延伸。最终有效长度
size就是答案;tails只保存最优结尾,不一定是一条真实子序列。
解题步骤
- 初始化
tails,有效长度为size = 0。- 依次遍历
nums,在tails[0, size)中二分查找第一个>= num的位置。- 用
num覆盖该位置;若位置等于size,则将size加一。- 遍历结束后返回
size。
代码实现
class Solution {
public int lengthOfLIS(int[] nums) {
int[] tails = new int[nums.length];
int size = 0;
for (int num : nums) {
int left = 0, right = size;
while (left < right) {
int mid = left + (right - left) / 2;
if (tails[mid] < num) {
left = mid + 1;
} else {
right = mid;
}
}
tails[left] = num;
if (left == size) {
size++;
}
}
return size;
}
}
func lengthOfLIS(nums []int) int {
tails := make([]int, len(nums))
size := 0
for _, num := range nums {
left, right := 0, size
for left < right {
mid := left + (right-left)/2
if tails[mid] < num {
left = mid + 1
} else {
right = mid
}
}
tails[left] = num
if left == size {
size++
}
}
return size
}
复杂度分析
- 时间复杂度:$O(n \log n)$,每个元素执行一次二分查找。
- 空间复杂度:$O(n)$,用于保存
tails。
关键点总结
tails[i]表示长度为i + 1的递增子序列的最小结尾。- 查找第一个
>= num的位置,才能保证“严格递增”。- 替换结尾不会改变已有长度,只会增加后续扩展的机会。
tails不一定是原数组中的真实子序列,答案只取其有效长度。
易错点总结
- 将二分条件写成查找第一个
> num,会把重复元素错误地计入答案。- 在整个
tails数组上二分,而不是只搜索有效区间[0, size)。- 替换已有结尾时误增
size;只有left == size时长度才增加。- 把子序列当成连续子数组,或先排序后再求长度。
相似题目
| 题目 | 难度 | 考察点 |
|---|---|---|
| 354. 俄罗斯套娃信封问题 | 困难 | 二维偏序,须按宽升序、等宽时高降序排序,再对高跑本题的贪心加二分 |
| 368. 最大整除子集 | 中等 | 可接条件从「更大」换成「能整除」,必须先排序保证传递性,且要还原具体子集 |
| 646. 最长数对链 | 中等 | 数对可接关系能用按右端点排序的区间贪心解决,不必退回本题的状态设计 |
| 673. 最长递增子序列的个数 | 中等 | 除长度外还要统计取到最长的方案数,需要额外一个计数数组与 dp 同步转移 |
| 674. 最长连续递增序列 | 简单 | 要求连续,状态只依赖前一个元素,一次线性扫描即可,正好对照子序列与子数组 |
| 1048. 最长字符串链 | 中等 | 可接条件是「删掉一个字符能得到」,要先按长度分组并用哈希表查前驱,无法二分 |
| 面试题 08.13. 堆箱子 | 困难 | 长宽高三维都要严格递增,排序只能消掉一维,剩下两维仍需枚举前驱 |
| 面试题 17.08. 马戏团人塔 | 中等 | 与 354 同构的身高体重二维偏序,可直接套用同一套排序加二分模板 |