0300.最长递增子序列

难度:🟡 中等

标签数组动态规划二分查找

链接300. 最长递增子序列

题目描述

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

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

示例 1:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

示例 2:

输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:

输入:nums = [7,7,7,7,7,7,7]
输出:1

解题思路

核心思想

本题的目标是找到数组中“最长”的“递增”“子序列”。这是一个经典的动态规划问题。核心思想是定义一个状态,通过解决子问题来构建最终的解。

思路选择

动态规划 (Dynamic Programming) 是解决此问题的标准思路。

  • 我们可以定义一个 dp 数组,其中 dp[i] 表示以 nums[i] 这个数结尾的最长递增子序列的长度。

  • 为了计算 dp[i],我们需要回顾在 i 之前的所有元素 nums[j](其中 j < i)。

  • 如果 nums[j] < nums[i],这意味着 nums[i] 可以接在以 nums[j] 结尾的递增子序列的后面,形成一个更长的递增子序列。因此,我们可以更新 dp[i] 的值为 max(dp[i], dp[j] + 1)

  • 遍历完所有可能的 j 之后,dp[i] 就得到了最终值。

  • 整个 dp 数组中的最大值,就是整个 nums 数组的最长递增子序列的长度。

关键步骤

  1. 处理边界情况:如果数组为空,返回 0。

  2. 初始化:创建一个与 nums 等长的 dp 数组,并用 1 填充。因为每个元素自身都构成一个长度为 1 的递增子序列。

  3. 双层循环: a. 外层循环遍历 i 从 1 到 nums.length - 1。 b. 内层循环遍历 j 从 0 到 i - 1。 c. 在内层循环中,如果 nums[j] < nums[i],则更新 dp[i] = Math.max(dp[i], dp[j] + 1)

  4. 返回结果:遍历结束后,dp 数组中的最大值即为所求答案。

代码实现

/**
 * @param {number[]} nums
 * @return {number}
 */
var lengthOfLIS = function (nums) {
    if (nums.length === 0) {
        return 0;
    }

    // dp[i] 表示以 nums[i] 结尾的最长递增子序列的长度
    let dp = new Array(nums.length).fill(1);
    let maxLength = 1;

    for (let i = 1; i < nums.length; i++) {
        for (let j = 0; j < i; j++) {
            if (nums[j] < nums[i]) {
                dp[i] = Math.max(dp[i], dp[j] + 1);
            }
        }
        // 更新全局最长长度
        maxLength = Math.max(maxLength, dp[i]);
    }

    return maxLength;
};

复杂度分析

  • 时间复杂度O(n²) 其中 n 是数组 nums 的长度。我们使用了两层嵌套循环。

  • 空间复杂度O(n) 我们需要一个与输入等长的 dp 数组来存储中间状态。

相关题目

总结

本题是动态规划领域的经典问题。O(n²) 的解法清晰地展示了如何通过定义状态和状态转移方程来解决问题。此外,本题还存在一个更为高效的 O(n log n) 解法,它通过维护一个有序的“牌堆”数组并结合二分查找来优化状态的更新过程,是进一步深入学习的绝佳案例。

Copyright © Jun 2025 all right reserved,powered by Gitbook该文件修订时间: 2025-07-03 17:35:08

results matching ""

    No results matching ""

    results matching ""

      No results matching ""