300. Longest Increasing Subsequence

26 年 3 月 9 日 星期一
1464 字
8 分钟

300. Longest Increasing Subsequence

Screenshot 2026-03-09 at 4.44.38 pm
js
/**
 * @param {number[]} nums
 * @return {number}
 */
var lengthOfLIS = function (nums) {
  // 获取数组长度,处理边界情况:空数组直接返回0
  const n = nums.length
  if (n === 0) return 0

  // 初始化记忆化缓存数组,用于存储已经计算过的dfs(i)结果,避免重复计算
  // 初始值为-1,表示该位置尚未计算
  const memo = new Array(n).fill(-1)

  /**
   * 深度优先搜索函数:计算以nums[i]为结尾的最长递增子序列长度
   * @param {number} i - 当前元素的索引
   * @return {number} - 以nums[i]结尾的最长递增子序列长度
   */
  const dfs = (i) => {
    // 如果当前索引的结果已经计算过,直接返回缓存值(记忆化核心)
    if (memo[i] !== -1) return memo[i]

    // 初始化结果为0:表示初始状态下,没有比nums[i]小的前驱元素
    let res = 0

    // 遍历i之前的所有元素(j < i),寻找可以作为nums[i]前驱的元素
    for (let j = 0; j < i; j++) {
      // 如果nums[j] < nums[i],说明nums[j]可以作为nums[i]的前驱
      if (nums[j] < nums[i]) {
        // 递归计算以nums[j]为结尾的最长递增子序列长度,并更新res为最大值
        res = Math.max(res, dfs(j))
      }
    }

    // 最终结果:找到的最长前驱子序列长度 + 1(加上当前元素nums[i]本身)
    // 并将结果存入缓存,供后续复用
    memo[i] = res + 1
    return memo[i]
  }

  // 遍历数组中每个元素作为子序列结尾,计算所有dfs(i)的最大值,即为整个数组的LIS长度
  let maxLength = 0
  for (let i = 0; i < n; i++) {
    maxLength = Math.max(maxLength, dfs(i))
  }

  return maxLength
}

问:什么样的题目适合「选或不选」,什么样的题目适合「枚举选哪个」?

答:我分成两类问题:

相邻无关子序列问题(比如 0-1 背包),适合「选或不选」。每个元素互相独立,只需依次考虑每个元素选或不选。 相邻相关子序列问题(比如本题),适合「枚举选哪个」。我们需要知道子序列中的相邻两个数的关系。对于本题来说,枚举 nums[i] 必选,然后枚举前一个必选的数,方便比大小。如果硬要用「选或不选」,需要额外记录上一个选的数的下标,算法总体的空间复杂度为 O(n2),而枚举选哪个只需要 O(n) 的空间。

如果是相邻相关问题,用“以 i 结尾”或者“以 i 开头”思考;如果是相邻无关问题,用前缀或者后者思考,比如在 [0,i] 中选一个数,等于 j 的方案数。

相邻相关和相邻无关

相邻相关 & 相邻无关 核心解释

这两个概念是针对子序列 / 子集类动态规划问题的划分,核心区别在于:题目对选出的子序列中「元素间的相邻逻辑关系」是否有明确约束 / 依赖,简单说就是选出来的元素彼此之间要不要满足某种相邻的规则 / 比较关系

一、相邻无关(子序列元素无依赖)

核心特征

选出的子序列中,元素之间不需要满足任何相邻的逻辑关系,每个元素的选择是独立的,仅需考虑「选这个元素」或「不选这个元素」对结果的影响,最终结果只和「选了哪些元素」有关,和「元素间的相对顺序 / 大小 / 关联」无关。

典型例子

  • 0-1 背包问题:选物品时只考虑重量和价值,选 A 物品和选 B 物品之间没有任何依赖(不需要 A 和 B 满足大小、顺序等关系),仅需依次判断每个物品「选 / 不选」。
  • 子集和问题:判断是否能选出子集和为 target,元素间无任何约束,仅需独立考虑每个元素的选 / 不选。

解题思路:选或不选

对每个元素依次做决策:

  • 不选:当前结果继承前一个元素的决策结果;

  • 选:在满足题目基础条件的前提下,更新当前结果。

    无需记录上一个选的元素是什么,因为元素间无依赖。

二、相邻相关(子序列元素有依赖)

核心特征

选出的子序列中,相邻元素之间必须满足某种明确的逻辑约束(比如本题的严格递增、最长递减子序列的严格递减、最长公共子序列的顺序匹配等),后一个元素的选择依赖于前一个选的元素,必须通过前一个元素的状态来判断当前元素是否能选。

典型例子

  • 本题:最长严格递增子序列(LIS):要求子序列严格递增,因此选第i个元素作为子序列末尾时,必须知道前一个选的是哪个元素,并判断前一个元素的值是否小于nums[i]
  • 最长公共子序列(LCS):要求子序列元素在原数组中顺序一致,因此当前元素的选择依赖于两个数组前一个位置的匹配状态。

解题思路:枚举选哪个

通常固定最后一个选的元素(比如本题固定nums[i]为子序列末尾),然后向前枚举所有可能的前一个元素nums[j], j < i),判断是否满足相邻约束(nums[j] < nums[i]),再基于前一个元素的状态更新当前状态。

这种思路只需记录「以每个元素为末尾的最优解」,空间复杂度更低。

dfs(i) 的核心定义

dfs(i) 表示:以数组中第 i 个元素(也就是 nums[i])作为「最后一个元素」的最长递增子序列的长度

Screenshot 2026-03-10 at 8.40.49 pm

文章标题:300. Longest Increasing Subsequence

文章作者:Sirui Chen

文章链接:https://blog.siruichen.me/posts/300_longest_increasing_subsequence[复制]

最后修改时间:


商业转载请联系站长获得授权,非商业转载请注明本文出处及文章链接,您可以自由地在任何媒体以任何形式复制和分发作品,也可以修改和创作,但是分发衍生作品时必须采用相同的许可协议。
本文采用CC BY-NC-SA 4.0进行许可。