算法:最长递增子序列解法记录

题目

给你一个整数数组 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

题目分析

给定一个整数数组 nums,要求找到其中最长的严格递增子序列的长度。严格递增意味着每个数都比前一个数大,也就是说序列中的每个元素都要比其前面的元素大。

举个例子,假设 nums = [10, 9, 2, 5, 3, 7, 101, 18],其中的最长严格递增子序列为 [2, 3, 7, 101],所以结果就是 4,也就是该子序列的长度。

思路

  • 子序列的定义 :子序列是数组中任意几个元素的顺序组合,可以不连续。比如,在 [3, 6, 2, 7] 中,[3, 2, 7] 或者 [6, 7] 都是子序列。

  • 递增的定义 :严格递增的子序列要求每一个数字都比前一个大,所以如果一个子序列是 [x1, x2, ..., xn],那么必须满足 x1 < x2 < ... < xn

  • 动态规划的思路 :我们可以使用动态规划来解决这个问题。我们定义一个数组 dp,其中 dp[i] 表示以 nums[i] 结尾的最长递增子序列的长度。初始时,每个位置的子序列长度至少为 1,因为每个元素本身就是一个递增子序列。

  • 递推公式 :对于每一个元素 nums[i],我们会检查它之前的所有元素 nums[j],如果 nums[i] 大于 nums[j],那么 nums[i] 可以延续以 nums[j] 结尾的递增子序列。因此,更新 dp[i]dp[i] = max(dp[i], dp[j] + 1),表示如果以 nums[j] 结尾的子序列可以扩展到 nums[i],则更新 dp[i] 的值。

  • 最终结果 :最长递增子序列的长度就是 dp 数组中的最大值。

详细解读

  • dp[i] 的定义

    • dp[i] 表示以 nums[i] 这个元素为结尾的最长递增子序列的长度。

    • 你可以想象一下,每次考虑到 nums[i] 时,你要确定之前所有比 nums[i] 小的元素 nums[j]j < i),并且看看是否可以把 nums[i] 加到这些子序列的末尾,来形成一个新的递增子序列。

    • 每一个位置的初始值是 1,因为至少 nums[i] 本身就是一个递增子序列(长度为 1)。

  • 如何更新 dp[i]

    • 我们会从每个元素 nums[i] 向前检查所有前面的元素 nums[j]j < i)。如果 nums[i]nums[j] 大,说明 nums[i] 可以接在 nums[j] 的递增子序列后面,形成一个新的递增子序列。

    • 所以,我们尝试更新 dp[i] 的值dp[i] = Math.max(dp[i], dp[j] + 1);

      这个式子的意思是:如果 nums[i] 大于 nums[j],则可以在 dp[j] 的基础上延伸一个新的元素 nums[i],所以 dp[i] 可能会变大。

      • dp[j] + 1:表示以 nums[j] 结尾的最长递增子序列长度,再加上当前的 nums[i]

      • Math.max(dp[i], dp[j] + 1):更新 dp[i],即保留之前的值和新的 dp[j] + 1 之间的较大值。

  • 如何求解最终的结果

    • 最终,我们需要得到 整个数组中最长递增子序列的长度 。而这个长度其实就是 dp 数组中的最大值,表示所有以不同元素结尾的最长递增子序列的最大长度。

    • 所以,我们用以下方式得到最终结果:

      maxLength = Math.max(maxLength, dp[i]);

      这会遍历 dp 数组,得到其中的最大值。

举例分析

假设有一个数组 nums = [10, 9, 2, 5, 3, 7, 101, 18],我们来看如何通过动态规划得到最长递增子序列的长度。

  1. 初始化 dp 数组为:

    dp = [1, 1, 1, 1, 1, 1, 1, 1]

    因为每个元素初始时自己的递增子序列长度为 1。

  2. 遍历 nums 数组,更新 dp 数组:

    • i = 1, numsi = 9 ,检查所有 j < 1 的元素。没有比 9 小的元素,所以 dp[1] 还是 1。

    • i = 2, numsi = 2 ,检查所有 j < 2 的元素。没有比 2 小的元素,所以 dp[2] 还是 1。

    • i = 3, numsi = 5 ,检查所有 j < 3 的元素:

      • nums[0] = 10nums[0] > nums[3],不更新。

      • nums[1] = 9nums[1] > nums[3],不更新。

      • nums[2] = 2nums[2] < nums[3],更新 dp[3] = dp[2] + 1 = 2

    • i = 4, numsi = 3 ,检查所有 j < 4 的元素:

      • nums[0] = 10nums[0] > nums[4],不更新。

      • nums[1] = 9nums[1] > nums[4],不更新。

      • nums[2] = 2nums[2] < nums[4],更新 dp[4] = dp[2] + 1 = 2

      • nums[3] = 5nums[3] > nums[4],不更新。

    • i = 5, numsi = 7 ,检查所有 j < 5 的元素:

      • nums[0] = 10nums[0] > nums[5],不更新。

      • nums[1] = 9nums[1] > nums[5],不更新。

      • nums[2] = 2nums[2] < nums[5],更新 dp[5] = dp[2] + 1 = 2

      • nums[3] = 5nums[3] < nums[5],更新 dp[5] = Math.max(dp[5], dp[3] + 1) = 3

      • nums[4] = 3nums[4] < nums[5],更新 dp[5] = Math.max(dp[5], dp[4] + 1) = 3

    • 继续这个过程直到遍历完整个数组,最终得到

      dp = [1, 1, 1, 2, 2, 3, 4, 4]

  3. 最终结果: 最大的 dp[i]4,因此最长递增子序列的长度是 4

代码

java 复制代码
public class Solution {
    public int lengthOfLIS(int[] nums) {
        if (nums == null || nums.length == 0) {
            return 0; // 如果输入数组为空或没有元素,返回0
        }

        // dp[i] 表示以 nums[i] 结尾的最长递增子序列的长度
        int n = nums.length;
        int[] dp = new int[n];

        // 初始时,每个元素的子序列长度为1
        for (int i = 0; i < n; i++) {
            dp[i] = 1;
        }

        // 遍历每个元素,检查它之前的所有元素
        for (int i = 1; i < n; i++) {
            for (int j = 0; j < i; j++) {
                // 如果 nums[i] 大于 nums[j],那么 nums[i] 可以接在 nums[j] 后面
                if (nums[i] > nums[j]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1); // 更新 dp[i] 的值
                }
            }
        }

        // 找到 dp 数组中的最大值,表示最长递增子序列的长度
        int maxLength = 0;
        for (int i = 0; i < n; i++) {
            maxLength = Math.max(maxLength, dp[i]);
        }

        return maxLength;
    }

    public static void main(String[] args) {
        Solution solution = new Solution();
        int[] nums = {10, 9, 2, 5, 3, 7, 101, 18};
        System.out.println("Longest Increasing Subsequence Length: " + solution.lengthOfLIS(nums));
    }
}
相关推荐
禹凕2 小时前
滑动窗口算法实战指南
python·算法
DisonTangor2 小时前
【腾讯混元雪耻归来】 Hy4 preview:770B 参数 MoE 旗舰模型,1M 上下文全面开源
人工智能·算法·开源·aigc·腾讯云·腾讯云ai代码助手
明月_清风3 小时前
从二叉树到 B+ 树:一文搞懂工程中「树」的演化之道
数据结构·算法·go
渡我白衣3 小时前
并查集:基础认识与模拟实现
android·java·javascript·数据结构·c++·算法·并查集
hetao17338373 小时前
2026-09-01~09-04 hetao1733837 的刷题记录
c++·算法
Evand J4 小时前
【MATLAB例程,图像滤波5】 反谐波均值滑动窗口滤波(CHMF)图像降噪与质量评价,附代码下载链接
图像处理·算法·计算机视觉·matlab·均值算法·滑动窗口滤波·均值滑动
血小板要健康4 小时前
链表 阶段算法总结
java·数据结构·笔记·算法·leetcode·链表
a187927218314 小时前
【算法】回溯算法(三):三记重锤与 N 皇后——记忆化、状态设计与三层漏斗
算法·leetcode·go·剪枝·回溯·n皇后·算法讲解
HugoStudio_SWAN6 小时前
洛谷 P1420 / P1179 / B4262 最长连号、数字统计与词频统计——统计的三种面孔
c++·学习·程序人生·算法
青 春 记 忆7 小时前
LeetCode 350. 两个数组的交集 II|Python 解法详解
python·算法·leetcode