300. 最长递增子序列

中等 含交互动画

学完这道你应该能: 说清它为什么用「LIS」,而不是死记步骤; 合上页面,自己默写出核心代码; 用 30 秒把思路讲清楚。

AI 私教 · 就着本题动画讲
卡住了不用硬扛,按 8 步把这题真正走通。
从读题、试解、找法到手写代码,边问边打勾,适合第一次系统学算法的同学。

题目描述

找最长的严格递增子序列的长度(元素可不相邻)。

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

思路解析

长度 n 的数组有 2ⁿ 个子序列,逐个检查是否递增、再比长度,指数级爆炸,n 一大就算不动。痛点在于:每检查一个新子序列都要从头看一遍,前面算过的递增段全白算了

换个思路:把「求全局最长」拆成「以每个数结尾的最长是多少」这种小问题,并记进 dp。为什么这么拆能省?因为算 dp[i] 时只要回看前面:哪些数比我小(能接在我前面),就在它们的 dp 里挑最大的 +1。前面每个 dp[j] 都是算好存着的,直接查、绝不重算。

建表 · 全 1:表头是 nums 的值。每个 dp 先初始化为 1——最差情况,每个数自己就是一条长度 1 的递增序列。下面从左往右逐个修正。

dp[0..2] · 前面没更小的:前三个数 10、9、2:要么前面没有数,要么前面的数都不比自己小,接不上谁,dp 全保持 1。注意数组在下降,没有递增链可拼。

dp[3] (值5) · 回看:轮到 5。回看前面:10、9 都不比 5 小,接不上;2 比 5 小,能接!dp[3] = dp[2]+1 = 2(链是 2,5)。

dp[4] (值3) · 回看:轮到 3。前面比它小的只有 2(5 不算,5 大于 3),dp[4] = dp[2]+1 = 2(链是 2,3)。

dp[5] (值7) · 回看 j=2:重点拆 7 这一格——它要回看前面每一个 j。先看 j=2(值 2):2 小于 7,能接,候选长度 dp[2]+1 = 2。先记着这个候选。

dp[5] (值7) · 回看 j=3:继续回看 j=3(值 5):5 小于 7,能接,候选 dp[3]+1 = 3。比刚才的 2 更长,取大,候选升到 3(链 2,5,7)。j=4(值 3)也小于 7,但 dp[4]+1=3 没更大,不变。

dp[5] (值7) · 负例 j=0:负例:回看 j=0(值 10),10 不小于 7,接在它后面就不递增了,直接跳过,不更新。j=1(值 9)同理也跳过。所有 j 看完,dp[5] 取所有候选最大 = 3

dp[6] (值101) · 回看:101 比前面每个数都大,全都能接。回看时挑它们里 dp 最大的——是 dp[5]=3,所以 dp[6] = 3+1 = 4(链 2,5,7,101)。这就是目前最长的。

dp[7] (值18) · 回看:18 比 101 小(接不上 101),但比 7 等都大,挑 dp 最大的 dp[5]=3,dp[7] = 3+1 = 4(链 2,5,7,18)。

答案 = max(dp):答案是整张表的最大值(不是最后一个!),dp[6]、dp[7] 都是 4,所以最长递增子序列长度 = 4

DP 的本质是「最优子结构」——大问题的最优解由子问题的最优解拼成;「以 i 结尾」只是把子问题切出来的常用手法,每个 dp[i] 都是算好就记住的小答案。

参考代码(Python)

Python
1dp = [1] * len(nums)               # 每个数自成长度 1
2for i in range(len(nums)):
3    for j in range(i):             # 回看 i 前面每个数
4        if nums[j] < nums[i]:      # 严格小才能接
5            dp[i] = max(dp[i], dp[j] + 1)
6return max(dp)                     # 答案是整张表最大值

复杂度分析

  • 时间复杂度:O(n²) —— 每个 i 都要回看它前面所有 j,约 n×n/2 次
  • 空间复杂度:O(n) —— 一个长度 n 的 dp 数组

套路模板

记住骨架:dp[i] 以 i 结尾、内层回看 j、答案取 max(dp)。只要改 can_extend 这一行条件,最长数对链、俄罗斯套娃信封等一大类题都能套。

Python
1# 凡是「最长 / 最优子序列」都能套这个骨架
2dp = [1] * n                 # dp[i]: 以 i 结尾的最优解
3for i in range(n):
4    for j in range(i):       # 回看前面每个 j
5        if can_extend(j, i): # 满足转移条件才接
6            dp[i] = max(dp[i], dp[j] + 1)
7return max(dp)               # 答案在整张表里找,不是 dp[-1]

易错点

  • 错误写法:return dp[-1]正确写法:return max(dp)(最长链不一定以最后一个数结尾。本例 dp[-1]=dp[7]=4 碰巧对,但若把 18 换成小数 dp[-1] 就偏小,必须取整张表 max)
  • 错误写法:if nums[j] <= nums[i](用小于等于)正确写法:严格用 nums[j] < nums[i](题目要严格递增,相等不算递增;用 <= 会把 [2,2] 也当成长度 2,答案偏大)

以上文字与上方动画完全同一思路:先看动画建立直觉 → 读文字巩固 → 关掉页面自己默写一遍代码,能写出来才算真的掌握

下一题 →1143. 最长公共子序列 ← 返回题库