【力扣100题】47.最长递增子序列

题目描述

给你一个整数数组 nums,找到其中最长严格递增子序列的长度

子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如 [3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例:

  • 输入:nums = [10,9,2,5,3,7,101,18] → 输出:4(最长递增子序列是 [2,3,7,101],长度为 4)
  • 输入:nums = [0,1,0,3,2,3] → 输出:4
  • 输入:nums = [7,7,7,7,7,7,7] → 输出:1

解题思路总览

方法 思路 时间复杂度 空间复杂度
动态规划 dp[i] 表示以 nums[i] 结尾的最长递增子序列长度,状态转移 dp[i] = max(dp[j]+1) O(n^2) O(n)
二分查找 维护一个有序数组 tail,用二分查找更新,最长递增子序列长度即为 tail 的大小 O(n log n) O(n)

本题采用动态规划方法,并给出二分查找的进阶解法。


完整代码

动态规划版本:

cpp 复制代码
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        if (n == 0) return 0;
        vector<int> dp(n, 0);
        int ans = INT_MIN;
        for (int i = 0; i < n; i++) {
            dp[i] = 1;
            for (int j = 0; j < i; j++) {
                if (nums[j] < nums[i]) {
                    dp[i] = max(dp[i], dp[j] + 1);
                }
            }
            ans = max(ans, dp[i]);
        }
        return ans;
    }
};

二分查找版本(进阶 O(n log n)):

cpp 复制代码
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        vector<int> tail;
        for (int x : nums) {
            auto it = lower_bound(tail.begin(), tail.end(), x);
            if (it == tail.end()) {
                tail.push_back(x);
            } else {
                *it = x;
            }
        }
        return tail.size();
    }
};

算法流程图

复制代码
输入: nums = [10, 9, 2, 5, 3, 7, 101, 18]

动态规划过程:

初始化:
  n = 8
  dp[0...7] = 0
  ans = -∞

i = 0, nums[0] = 10:
  dp[0] = 1
  ans = 1

i = 1, nums[1] = 9:
  j = 0: 10 < 9? 否
  dp[1] = 1
  ans = 1

i = 2, nums[2] = 2:
  j = 0: 10 < 2? 否
  j = 1: 9 < 2? 否
  dp[2] = 1
  ans = 1

i = 3, nums[3] = 5:
  j = 0: 10 < 5? 否
  j = 1: 9 < 5? 否
  j = 2: 2 < 5? 是
    dp[3] = max(1, dp[2]+1) = 2
  dp[3] = 2
  ans = 2

i = 4, nums[4] = 3:
  j = 0: 10 < 3? 否
  j = 1: 9 < 3? 否
  j = 2: 2 < 3? 是
    dp[4] = max(1, dp[2]+1) = 2
  dp[4] = 2
  ans = 2

i = 5, nums[5] = 7:
  j = 0: 10 < 7? 否
  j = 1: 9 < 7? 否
  j = 2: 2 < 7? 是
    dp[5] = max(1, dp[2]+1) = 2
  j = 3: 5 < 7? 是
    dp[5] = max(2, dp[3]+1) = 3
  j = 4: 3 < 7? 是
    dp[5] = max(3, dp[4]+1) = 3
  dp[5] = 3
  ans = 3

i = 6, nums[6] = 101:
  ... (所有前面的数都 < 101)
  dp[6] = 4
  ans = 4

i = 7, nums[7] = 18:
  ... (10,9,2,5,3,7 < 18,但 18 < 101)
  dp[7] = 4
  ans = 4

最终 ans = 4
输出: 4

逐行解析

动态规划版本逐行解析:

cpp 复制代码
int n = nums.size();

含义: 记录数组长度。

cpp 复制代码
if (n == 0) return 0;

含义: 空数组没有递增子序列,直接返回 0。

cpp 复制代码
vector<int> dp(n, 0);

含义: dp[i] 表示以 nums[i] 结尾的最长递增子序列长度。初始化为 0,后续会被更新。

cpp 复制代码
int ans = INT_MIN;

含义: 记录全局最长递增子序列长度,初始化为最小值。

cpp 复制代码
for (int i = 0; i < n; i++)

含义: 遍历数组,以每个位置作为递增子序列的结尾。

cpp 复制代码
dp[i] = 1;

含义: 初始化 dp[i] = 1,表示以 nums[i] 结尾的递增子序列至少包含它自己。

cpp 复制代码
for (int j = 0; j < i; j++)

含义: 枚举所有在 i 之前的元素 j,尝试将其接到以 nums[j] 结尾的递增子序列后面。

cpp 复制代码
if (nums[j] < nums[i])

含义: 只有当 nums[j] 小于 nums[i] 时,才能将 nums[i] 接在以 nums[j] 结尾的递增子序列后面(严格递增)。

cpp 复制代码
dp[i] = max(dp[i], dp[j] + 1);

含义: 状态转移方程。如果将 nums[i] 接在 nums[j] 后面,新的递增子序列长度为 dp[j] + 1,取较大值更新 dp[i]

cpp 复制代码
ans = max(ans, dp[i]);

含义: 更新全局最长长度。

cpp 复制代码
return ans;

含义: 返回最长递增子序列的长度。


复杂度分析

方法 时间复杂度 空间复杂度
动态规划 O(n^2) O(n)
二分查找 O(n log n) O(n)

动态规划说明: 外层循环 n 次,内层循环最多 n 次,时间复杂度 O(n^2)。

二分查找说明: 遍历数组 n 次,每次二分查找 O(log n),总时间复杂度 O(n log n)。


面试追问 FAQ

问题 答案
动态规划中 dp[i] 的含义是什么? dp[i] 表示以 nums[i] 结尾的最长递增子序列长度。注意必须以 nums[i] 结尾
为什么不直接返回 dp[n-1] 因为最长递增子序列不一定以最后一个元素结尾,例如 [1, 2, 3, 0] 中最长递增子序列以索引 2 结尾而不是 3
二分查找版本中 tail 数组的含义是什么? tail[i] 表示长度为 i+1 的递增子序列的最小结尾元素。tail 数组是严格递增的
二分查找版本为什么用 lower_bound 而不是 upper_bound 因为要找的是「不小于 x」的位置,对于严格递增子序列要用 lower_bound(找第一个 >= x 的位置)
如何输出具体的递增子序列? 动态规划版本需要额外记录每个状态是从哪个 j 转移来的,然后从后往前回溯
进阶:如何修改代码求最长非递减子序列? if (nums[j] < nums[i]) 改为 if (nums[j] <= nums[i]),允许相等元素
进阶:如何求最长递减子序列? 将数组反转后求最长递增子序列,或将比较条件改为 >

相关题目

题号 题目 难度 核心思路
300 最长递增子序列 中等 动态规划/二分查找
354 俄罗斯套娃信封问题 困难 排序 + 最长递增子序列
673 最长递增子序列的个数 中等 动态规划 + 计数
491 递增子序列 中等 DFS + 去重

总结

要点 内容
核心思想 动态规划:以每个元素作为递增子序列的结尾,求最长长度
状态定义 dp[i] = 以 nums[i] 结尾的最长递增子序列长度
状态转移 dp[i] = max(dp[j] + 1),其中 j < inums[j] < nums[i]
初始化 dp[i] = 1(每个元素自身构成长度为 1 的递增子序列)
结果 返回 dp 数组中的最大值

相关推荐
木子墨5161 小时前
系统设计面试 | 实现一个限流器:滑动窗口 → 令牌桶 → 漏桶
java·开发语言·数据结构·数据库·面试·职场和发展
环流_1 小时前
Redis zinterstore
算法
不知名的忻2 小时前
交换排序:冒泡排序 vs 快速排序(Java)
java·算法·排序算法
沃普天科技2 小时前
USB显示器多屏异显多屏拼接IF8032 IT690 VL171 8801 RTD2556
arm开发·驱动开发·算法·计算机外设·音视频·硬件工程·pcb工艺
炽烈小老头2 小时前
【 每天学习一点算法 2026/05/14】单词接龙
学习·算法
yxc_inspire2 小时前
24年CCPC山东邀请赛补题
学习·算法
木子墨5162 小时前
工程算法实战 | 数据库ORDER BY的底层:内存排序 → 外部归并 → 索引优化
数据结构·数据库·python·sql·算法·动态规划
广州灵眸科技有限公司2 小时前
瑞芯微(EASY EAI)RV1126B 模型部署API说明
linux·开发语言·网络·人工智能·深度学习·算法·yolo
東隅已逝,桑榆非晚2 小时前
深⼊理解指针(5)
c语言·笔记·算法