300. 最长递增子序列(Longest Increasing Subsequence)

频次 ★★★★★ · 难度 🟡 · 高频:字节/阿里/美团

题目

整数数组 nums,找最长严格递增子序列(可不连续)的长度。

示例

输入: nums = [10,9,2,5,3,7,101,18]
输出: 4  ([2,3,7,101])

思路

两种解法

解法 1:O(n²) DPdp[i] 为以 nums[i] 结尾的 LIS 长度。dp[i] = max(dp[j] + 1 for j < i if nums[j] < nums[i])

解法 2:O(n log n) 贪心 + 二分 — 维护一个数组 tails,tails[k] 表示长度为 k+1 的递增子序列的最小末尾值。对每个 nums[i],二分查找它在 tails 中的位置并替换。

这道题 O(n log n) 解法是高频考点。

代码

// O(n log n) 贪心 + 二分
public int lengthOfLIS(int[] nums) {
    int[] tails = new int[nums.length];
    int len = 0;                              // tails 当前长度
    for (int x : nums) {
        int i = Arrays.binarySearch(tails, 0, len, x);
        if (i < 0) i = -(i + 1);              // 二分插入点
        tails[i] = x;
        if (i == len) len++;
    }
    return len;
}

复杂度

  • 时间:O(n log n) —— 每次二分 O(log n)
  • 空间:O(n)

边界条件

  • 空数组:返回 0
  • 全降序:每步 tails 都被覆盖在第 0 位,len = 1
  • 全升序:tails 顺序增长,len = n

变式

易错点

  • tails 中的顺序不是正确的 LIS 序列——它只维护了每个长度的最小末尾,不能用于还原子序列。面试追问如果问还原,需要回溯 parent 指针或另开一个 DP 数组记录
  • binarySearch 没找到时返回 -(insertionPoint) - 1
  • 二分条件是 <(严格递增),如果要求非递减需要改比较条件

面试追问

  • O(n²) DP 写法? 双重循环,面试中作为”先给一个简单解”的铺垫,再优化到 O(n log n)

关联题