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

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

相关推荐
不会就选b4 小时前
算法日常・每日刷题--<贪心>14
算法
mmmmath_37 小时前
LeetCode.541.反转字符串II
数据结构·算法·leetcode
Navigator_Z7 小时前
LeetCode //MySQL - 1251. Average Selling Price
c语言·算法·leetcode
醇氧8 小时前
MySQL 8.0 系统表损坏与引擎转换故障排查实战
数据结构·算法
大熊背8 小时前
《Color constancy by characterization of illumination chromaticity》之色度色域最大化算法(二)
算法·白平衡·色度·色温
钓鱼的肝8 小时前
梳理(1-5)
c++·经验分享·笔记·算法·青少年编程
HZZD_HZZD9 小时前
CSDN_批发市场水电漏损归因算法LAM的原理与落地
嵌入式硬件·物联网·算法
shirsl11 小时前
算法 Day 5 树 / 二叉树 + DFS
数据结构·python·算法
木子算法11 小时前
非凸、离散、还耦合:论文里的求解方法是一条四步流水线
人工智能·算法·目标跟踪