代码随想录算法训练营 Day37 | 动态规划 part10

300. 最长递增子序列

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

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

cpp 复制代码
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        // dp[i] 的定义:以 nums[i] 结尾的,最长递增子序列的长度
        // 初始化:每个元素自身至少构成长度为 1 的子序列
        vector<int> dp(n, 1); // 原代码用了 n+1,其实 n 就足够了,因为下标 0 到 n-1 刚好对应 n 个元素
        int res = 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);
                }
            }
            // 【微小优化】:把更新最大值的步骤挪出内层循环,减少不必要的判断执行次数
            res = max(res, dp[i]); 
        }
        return res;
    }
};

总结

1. dp[i] 定义的"陷阱"

最容易踩的坑:误把 dp[i] 理解为"前 i 个元素中的最长递增子序列长度"。

  • 如果是这样理解,dp[i]dp[i-1] 之间根本无法建立直接联系,代码写不出来。
  • 正确定义:dp[i] 必须是以 nums[i] 结尾的长度。这意味着 nums[i] 必须被选中。有了这个强制前提,才能去前面找比它小的数来"接龙"。
2. 为什么时间复杂度是 O(n^2)?

外层 i 遍历每个数,内层 j 让当前数 nums[i] 去它前面所有的数里"找备胎"。找到一个比它小的,就把它的长度拿过来 +1。这是最朴素的暴力枚举。


674. 最长连续递增序列

给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。

连续递增的子序列 可以由两个下标 lrl < r)确定,如果对于每个 l <= i < r,都有 nums[i] < nums[i + 1] ,那么子序列 [nums[l], nums[l + 1], ..., nums[r - 1], nums[r]] 就是连续递增子序列。

cpp 复制代码
class Solution {
public:
    int findLengthOfLCIS(vector<int>& nums) {
        int n = nums.size();
        // dp[i] 定义:以 nums[i] 结尾的,最长连续递增子序列的长度
        // 初始化:每个元素自身长度为 1
        vector<int> dp(n, 1);
        int res = 1;
        for(int i = 1; i < n; i++){
            // 【核心变化】:因为要求"连续",nums[i] 只能接在它正前方的 nums[i-1] 后面
            // 不需要像 300 题那样写内层循环去遍历前面的所有元素
            if(nums[i] > nums[i-1]) {
                dp[i] = dp[i-1] + 1;
            }
            // 【微小优化】:同理,将判断移出,直接使用 max 函数更简洁
            res = max(res, dp[i]);
        }
        return res;
    }
};

总结

1. 为什么去掉了内层 for 循环?
  • 300题(不连续/子序列):[1, 3, 5, 2, 8]。算 8 的时候,它可以接在 1352 任何一个后面,所以必须用 j0 遍历到 i-1 去找所有比它小的数。复杂度 O(n^2)。
  • 本题(连续/子数组):[1, 3, 5, 2, 8]。如果要求连续,8 只能接在紧挨着它的 2 后面。如果 82 大,长度就是 2的长度+1;如果比 2 小,连续断裂,长度重置为 1。它没有其他选择,所以不需要内层循环。
2. 进阶思考:连 dp 数组都不需要

正是因为 dp[i] 仅仅依赖于 dp[i-1],根本不需要记住之前所有的状态。这道题完全可以把空间复杂度降维到 O(1):

cpp 复制代码
int count = 1, res = 1;
for(int i = 1; i < n; i++) {
    if(nums[i] > nums[i-1]) count++;
    else count = 1; // 断裂直接重置
    res = max(res, count);
}

718. 最长重复子数组

给两个整数数组 nums1nums2 ,返回 两个数组中 公共的 、长度最长的子数组的长度

cpp 复制代码
class Solution {
public:
    int findLength(vector<int>& nums1, vector<int>& nums2) {
        int n = nums1.size(), m = nums2.size();
        // dp[i][j] 定义:以 nums1[i-1] 和 nums2[j-1] 结尾的,最长公共子数组的长度
        // 初始化为 0(代表没有公共部分)
        vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));
        int res = 0;        
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= m; j++){
                // 只有当两个数相等时,才能接上之前的公共长度
                if(nums1[i-1] == nums2[j-1]){
                    // 状态转移:等于左上角的状态 + 1
                    dp[i][j] = dp[i-1][j-1] + 1;
                    // 实时更新全局最大值
                    res = max(res, dp[i][j]);
                }
                // 如果不相等,dp[i][j] 默认就是 0,不需要额外写 else
            }
        }
        return res;
    }
};

总结

1. 为什么 dp 数组要开 (n+1, m+1) 的大小?

防越界,减心智负担。

如果不开大一格,直接用 dp[n][m],当比较 nums1[0]nums2[0] 时,公式需要找 dp[-1][-1],这在代码里会直接报错。

通过整体向后偏移一位,dp[i][j] 实际上对应的是 nums1[i-1]nums2[j-1]。这样 dp[0][j]dp[i][0] 永远是 0,作为天然的安全垫,内层循环直接从 1 开始跑,极其清爽。

2. 为什么公式是找"左上角" dp[i-1][j-1]

这是"连续"带来的铁律。

  • 这里是子数组(必须连续),既然 nums1[i-1]nums2[j-1] 强行绑定了,那么它们前面的那一坨必须是 nums1[i-2]nums2[j-2] 绑定的结果。所以只能走对角线,看左上角。
3. 为什么没有 else 分支?

在本题(连续)中,如果不相等,意味着以这两个元素结尾的公共子数组长度直接断崖式归零。因为初始化就是 0,所以不相等时什么都不用做,天然就是 0。

相关推荐
baizhigangqw2 小时前
启发式算法WebApp实验室:从搜索策略到群体智能的能力进阶(二)
算法·启发式算法·web app
alphaTao2 小时前
LeetCode 每日一题 2026/4/13-2026/4/19
算法·leetcode·职场和发展
灵智实验室2 小时前
PX4姿态解算技术详解(四):姿态更新/递推与共锥补偿
算法·无人机·px 4
良木生香2 小时前
【C++初阶】C++编程基石:编码表&&STL的入门指南
c语言·开发语言·数据结构·c++·算法
秋92 小时前
学霸圈公认的 10 种高效学习习惯:从低效到顶尖的底层逻辑
人工智能·学习·算法
极简车辆控制2 小时前
泵控式电液主动悬架系统分层控制研究_论文复现
算法·汽车
扶苏xw3 小时前
【分组背包】
算法·动态规划
李兆龙的博客3 小时前
从一到无穷大 #68 Agent Memory 全景:大模型智能体记忆机制的形态、动态与前沿
大数据·人工智能·算法
cwplh3 小时前
平衡树学习笔记
数据结构·笔记·学习·算法