300. 最长递增子序列(中等)
Blind 75 › 300. 最长递增子序列(中等) (第 10 章)
题目
给定一个整数数组 nums,求其中最长严格递增子序列的长度。
子序列是从原数组中删除若干元素(也可以一个都不删)后,保持剩余元素相对顺序得到的序列;注意“子序列”不要求连续。
自造示例:nums = [4, 10, 4, 5, 7, 9] → 4。一条最长递增子序列是 [4, 5, 7, 9](对应下标 0、3、4、5),所以答案是 4。
再比如 nums = [7, 7, 7] → 1:相等元素不能构成“严格递增”,每个单元素自己就是一段。
约束:1 ≤ n ≤ 2500;-104 ≤ nums[i] ≤ 104。进阶目标:能否把时间复杂度降到 O(n log n)。
思路
一句话:设 dp[i] = 以 nums[i] 结尾的最长严格递增子序列长度,最后返回 max(dp)。
先想最直白的:把所有子序列都列出来再选最长。数组有 2n 个子序列,n 最大 2500,指数级完全不可行;即使写「选 / 不选」的递归,分支规模也不会变小。
关键观察:一个递增子序列最后一定落在某个下标 i。如果固定结尾是 nums[i],那它前面的部分必然是一个以 j < i 结尾、且满足 nums[j] < nums[i] 的递增子序列。于是只要算出所有 dp[j],就能推出 dp[i]:
- 转移:
dp[i] = max(1, 1 + dp[j]),其中j < i且nums[j] < nums[i]。 - 边界:每个元素自己都能构成长度 1 的子序列,所以
dp[i]初始为 1。
这就是一维动态规划:从左到右填 dp,每个位置枚举它所有可能的前驱即可。
解法
- 初始化
dp数组,每个位置为1,并用result记录全局最大,初值为1。 - 外层枚举结尾
i,内层枚举前驱j < i。 - 若
nums[j] < nums[i],尝试把nums[i]接在j后面:dp[i] = max(dp[i], dp[j] + 1)。 - 每一步更新
result = max(result, dp[i]),最后返回result。
以 [4, 10, 4, 5, 7, 9] 为例逐步填表:
| i | nums[i] | 可接在哪些 j 后面 | dp[i] |
|---|---|---|---|
| 0 | 4 | 无 | 1 |
| 1 | 10 | 0(4<10) | 2 |
| 2 | 4 | 无(前面没有更小) | 1 |
| 3 | 5 | 0、2(4<5) | 2 |
| 4 | 7 | 0、2、3(5<7) | 3 |
| 5 | 9 | 0、2、3、4(7<9) | 4 |
结果 max(dp) = 4。此例中 dp 的最后一个位置恰好是答案,但这并非必然——最长子序列不必以最后一个元素结尾。
下图把上面 4 个步骤串成流程,并逐步演示:


C++
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
if (n == 0) return 0;
vector<int> dp(n, 1); // 以 nums[i] 结尾的最长长度,至少是自身
int result = 1;
for (int i = 1; i < n; ++i) {
for (int j = 0; j < i; ++j) {
if (nums[j] < nums[i]) { // 严格递增
dp[i] = max(dp[i], dp[j] + 1);
}
}
result = max(result, dp[i]); // 答案未必在最后一个位置
}
return result;
}
};Python
Python 3 示例
还没有评论,来说两句?