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

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

相关推荐
元启数宇1 小时前
幕墙AI设计算法逻辑:元启数宇如何智能分格
人工智能·算法
宵时待雨1 小时前
优选算法专题16:多源BFS
算法·宽度优先
重生之后端学习1 小时前
53. 最大子数组和[中等]✅
java·数据结构·算法·leetcode·职场和发展
INGNIGHT2 小时前
1908 · 布尔表达式求值(stack单调栈&dfs)
开发语言·c++·算法
lucas_AI2 小时前
把表格压成 64 个 token,长文档问答反而更准了:先找表,再看数
人工智能·深度学习·算法
Zzz 小生2 小时前
Lazy Theta*:把昂贵的视线检测留到“真正需要”时再做
人工智能·算法·贪心算法·推荐算法
l1258653 小时前
# LangGraph Deep Research Agent 全流程设计:多轮研究、人机协同与真实来源管理
数据库·人工智能·python·算法·自然语言处理·oracle·langchain
青少儿编程课堂4 小时前
图形化编程实战:智能交通灯调度台,一个作品讲透循环、条件与广播
c++·python·算法·bfs·信息学竞赛
evans在进步4 小时前
LeetCode 438 找到字符串中所有字母异位词:滑动窗口与排序解法详解
算法·leetcode·职场和发展
M78佐菲4 小时前
Linux多线程:创建、回收与互斥同步
linux·笔记·学习·算法