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

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

相关推荐
小π军31 分钟前
最大重叠区间数量
数据结构·算法
镜像视界(浙江)科技有限公司41 分钟前
《视频孪生之上:二维展示终结,三维空间计算重构城市逻辑》——跨摄像连续表达 × 三角测量厘米级定位 × 动态轨迹建模,构建新一代城市空间
大数据·人工智能·算法·矩阵·音视频·空间计算
a187927218311 小时前
【算法】双指针与滑动窗口(二):滑动窗口——吃进、判定、吐出
算法·leetcode·双指针·滑动窗口·原理·模板·算法讲解
铭哥的编程日记1 小时前
从一道 LeetCode Hard 到吃透一类题:加权区间调度「排序 + 二分 + DP」
算法·leetcode·职场和发展
Navigator_Z2 小时前
LeetCode //C - 1248. Count Number of Nice Subarrays
c语言·算法·leetcode
wzdark2 小时前
多维数组在算法设计中的存储映射问题4
算法
Phil3233 小时前
多智能体不是越多越好:Google《Towards a Science of Scaling Agent Systems》论文深度解读
算法
6Hzlia3 小时前
【Classic 150 刷题计划】 LeetCode 26. 删除有序数组中的重复项 | C++ 快慢双指针经典模板
c++·算法·leetcode
huang5791474 小时前
基于滑动窗口的流式数据算法优化思路3
算法
Ulyanov4 小时前
AudioVision Pro:基于 PySide6 + sounddevice 的实时音频可视化播放器设计
python·算法·音视频