dp子序列问题,好难呀

好吧学会了之后并不难。

dpi的含义为以i为结尾的最长子序列长度,注意:不是0-i之间最长子序列的长度!是以i为结尾的长度!

所以设对于每一个i,j从0--i-1进行遍历,如果numsi>numsj,说明这个j结尾的最长子序列可以加一个数字了,dpi就是numsj+1,取最大,当然也可能保留上一个j的最大值,因此是:

复制代码
(nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);

因为dpi不是0-i之间最长子序列的长度,所以最后返回的值不是dp【len-1】,而是对于每一个i,0-len取一个最大值,所以代码是:

cpp 复制代码
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        if (nums.size() <= 1) return nums.size();
        vector<int> dp(nums.size(), 1);
        int result = 0;
        for (int i = 1; i < nums.size(); i++) {
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
            }
            if (dp[i] > result) result = dp[i]; // 取长的子序列
        }
        return result;
    }
};

最长重复子数组

dpij :以下标i - 1为结尾的A,和以下标j - 1为结尾的B,最长重复子数组长度为dpij。 (特别注意: "以下标i - 1为结尾的A" 标明一定是 以Ai-1为结尾的字符串 )

此时细心的同学应该发现,那dp00是什么含义呢?总不能是以下标-1为结尾的A数组吧。

其实dpij的定义也就决定着,我们在遍历dpij的时候i 和 j都要从1开始。

那有同学问了,我就定义dpij为 以下标i为结尾的A,和以下标j 为结尾的B,最长重复子数组长度。不行么?

行倒是行! 但实现起来就麻烦一点,需要单独处理初始化部分,在本题解下面的拓展内容里,我给出了 第二种 dp数组的定义方式所对应的代码和讲解,大家比较一下就了解了。

  1. 确定递推公式

根据dpij的定义,dpij的状态只能由dpi - 1j - 1推导出来。

即当Ai - 1 和Bj - 1相等的时候,dpij = dpi - 1j - 1 + 1;

根据递推公式可以看出,遍历i 和 j 要从1开始!

  1. dp数组如何初始化

根据dpij的定义,dpi0 和dp0j其实都是没有意义的!

但dpi0 和dp0j要初始值,因为 为了方便递归公式dpij = dpi - 1j - 1 + 1;

所以dpi0 和dp0j初始化为0。

举个例子A0如果和B0相同的话,dp11 = dp00 + 1,只有dp00初始为0,正好符合递推公式逐步累加起来。

  1. 确定遍历顺序

外层for循环遍历A,内层for循环遍历B。

为什么两层循环都用 <= size(),而不是第一层用 <= size1、第二层用 <= i(类似三角形遍历)?

答案是:这是最长公共子串/子序列问题,必须遍历整个二维DP表,不能用三角形遍历。


1. 为什么必须遍历整个 size1 × size2 矩形?

因为 dp[i][j] 依赖的是 dp[i-1][j-1](左上方),而不是依赖同一行或同一列的前一个值。

j=0 j=1 j=2 j=3
i=0 0 0 0 0
i=1 0 dp00 dp01 dp02
i=2 0 dp10 dp11 dp12
i=3 0 dp20 dp21 dp22

每个 dp[i][j] 的值只取决于 左上角 dp[i-1][j-1],也就是说:

  • 计算第 i 行时,需要第 i-1 行的所有列的数据

  • 不能像三角形遍历那样只算一半,因为 对角线上的每个点都可能产生新的最长子串


2. 三角形遍历(j <= i)为什么不适用?

如果你写成:

cpp

复制代码
for (int i = 1; i <= nums1.size(); i++) {
    for (int j = 1; j <= i; j++) {  // ❌ 三角形遍历
        // ...
    }
}

那么:

  • nums1.size() = 5, nums2.size() = 10 时,你只比较了 5×5/2 = 12.5 个组合,漏掉了 5×10 - 12.5 = 37.5 个组合

  • 比如 nums1[4]nums2[9] 可能相等,但 j=9 > i=4,根本不会进入循环,会漏掉正确答案

因为最长公共子串不一定出现在两个数组的"前 i 个"对应位置------nums1 的第 1 个元素可以和 nums2 的第 10 个元素匹配。


3. 什么情况下用三角形遍历?

三角形遍历(j <= i)只适用于完全平方/矩阵对称问题,例如:

  • 计算字符串内部的最长回文子串dp[i][j] 只依赖 dp[i+1][j-1],且只需要上三角)

  • 矩阵链乘、最优二叉搜索树等区间DP

你的问题是双序列匹配 ,两个序列的长度可能不同,且匹配关系没有任何"位置对称"限制,所以必须遍历全矩形

相关推荐
wabs66610 小时前
关于图论【卡码网117.软件构建的思考】
数据结构·算法·软件构建·图论·卡码网
mifengxing10 小时前
LeetCode 41.缺失的第一个正数|Hard题O(n)+O(1)最优解法深度解析
java·算法·leetcode·排序算法
Tongzhi202611 小时前
从部署到运维:通芝科技无感考勤一体机的全流程效率解析
运维·数据结构·科技·算法·贪心算法
Wang's Blog11 小时前
AI Agent白手起家30: 动态示例选择器之根据长度选择 Few Shot 示例
算法
oyguyteggytrrwwwrt13 小时前
自制交叉线路识别算法
算法
手写码匠13 小时前
华为云Flexus+DeepSeek征文|Dify 多智能体协同编排实战:R1 规划 + V3 执行,构建企业 Agent 团队
人工智能·深度学习·算法·aigc
晓天衡宇•评测社区14 小时前
FSR-Bench 前沿科学推理榜单发布:GPT-5.5 居首,“会推理”未必“能答对”
算法
程序喵大人15 小时前
【C++进阶】STL算法与函数对象 - 02 sort为什么需要随机访问迭代器
开发语言·c++·算法
hanlin0315 小时前
动态规划专练:力扣第121、122题
笔记·算法·leetcode