子序列的DP奥秘

DP从入门到精通2.1(线性DP,上升子序列,公共子序列) emmm,dp问题真的很有魔力,你想得到状态表示方程,那么你马上就能起飞,如果你想不到,没思路那么......建议吃顿好的再来写可能会快点.....其实dp更多的是一种经验,你见得多,写的自然就快 阅读详情

目录

简介

最长递增子序列

摆动序列

小总结

最长递增子序列的个数

最长数对链

最长定差子序列

最长的斐波那契子序列的长度

最长等差数列

等差数列划分Ⅱ

总结


简介

上一节当中,我们学的是子数组,这个是连续的,比如123这个数组,求得都是1,12,123,2,23,3这一类连续的子数组

因为子数组是连续的,所以 “以 i 结尾的子数组” 只有两种可能:

  1. 接在 “以 i-1 结尾的子数组” 后面(延续连续区间);
  2. 仅包含 i 自己(重新开始一个连续区间)。这就决定了子数组 DP 的状态dp[i]只能依赖dp[i-1],转移逻辑极其固定。

而本节学的子序列,不一定是连续的

子序列的 “不连续性” 意味着:

  • 以 i 结尾的子序列,可能接在任意一个 j < i 且满足条件的位置后面(而非仅 i-1);
  • 比如 [1,3,2,4] 中,以 4 结尾的子序列,可接在 1、3、2 后面,需遍历所有 j 找最大值。

而子数组的 “连续性” 直接锁死了依赖范围 —— 只能接在 i-1 后面,无需遍历更前面的元素。

子序列的选法包含了子数组,子序列的选法有2^N,也就是一个数字有选和不选两种情况

但子数组不是,子数组有n+(n-1)+(n-2)种,即N^2种选法

所以子序列的选法多余子数组,复杂度高

最长递增子序列

经验+题目要求,以i位置为结尾的最长递增子序列

然后推导状态转移方程,发现一个状态刚好够

此时需要向前遍历,nums[i]和nums[j]进行对比

因为你当前子序列的构成,除了自己本身以外,还可能跟在前面任何一个数字的后面,我们需要对所有的子序列求一个最长的填到dp表当中

300. 最长递增子序列 - 力扣(LeetCode)

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int m=nums.size();
        vector<int>dp(m,1);
        for(int i=0;i<m;i++){
            for(int j=i;j>=0;j--){
                if(nums[i]>nums[j])
                dp[i]=max(dp[i],dp[j]+1);
            }
        }
        int ret=dp[0];
        for(int i=0;i<m;i++){
            ret=max(ret,dp[i]);
        }
        return ret;
    }
};

摆动序列

376. 摆动序列 - 力扣(LeetCode)

一个状态是无法表示的,因为你最后结束可能是下降也可能是上升,为什么子数组那里可以一个状态,因为子数组是连续的,你仅仅需要比较i i-1 i-2 即可,但是这里不行,因为不是连续的,所以导致比较不行

所以需要定义两个状态,明确表示最后是上升还是下降

很明显时间复杂度为O(n^2),因为你每次填一个位置,都要往前面找一个最大的状态

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        int m=nums.size();
        vector<int> f(m,1),g(m,1);
        for(int i=1;i<m;i++){
            for(int j=i-1;j>=0;j--){
                if(nums[i]>nums[j]){
                    //找下降
                    f[i]=max(g[j]+1,f[i]);
                }
                if(nums[i]<nums[j]){
                    //找上升
                    g[i]=max(f[j]+1,g[i]);
                }
            }
        }
        return max(f[m-1],g[m-1]);
    }
};

小总结

所以,子序列无非就是往前一个个找适合的,子数组是往i-1位置找,一个是不连续一个是连续

不连续注定了有时候时间复杂度是O(n^2)

最长递增子序列的个数

673. 最长递增子序列的个数 - 力扣(LeetCode)

前置知识,如何一遍扫描数组,找到最大的数已经其对应的个数

只需要一个小贪心算法即可

本题定义dp[i]一个状态的时候发现无法完成,因为推状态转移方程的时候推不出来,即当前位置的更新依赖最长的长度,已经最长长度的个数

所以我们需要两个状态

考虑两种情况

1:当前位置单独是一个子序列,那此时len[i]=1

2:当前位置和前进的j位置能够形成递增子序列,那此时我们需要找到最长的,并且找到最长当中的最多的个数

对于第一种情况,我们只需要初始化dp表的时候将两个表都初始化为1即可

对于第二种我们要分情况讨论

a.当j位置+1长度==len[i]时,说明此时是最长的,需要统计个数相加

b.当长度小于时,说明不是最长的,忽略

c.当长度大于时,说明找到新的最长的,需要重新统计,统计的个数应当变为count[j]

为什么要比较+1,是因为这些情况都是以i位置结尾了

对于返回值,完全可以在填dp表的时候,填完某个位置的时候就更新,否则你完整的填完dp表之后,还需要遍历两次才能找到返回值

class Solution {
public:
    int findNumberOfLIS(vector<int>& nums) {
        int m = nums.size();
        vector<int> len(m, 1), count(m, 1);
        int retlen = -1, retcount = -1; // 存结果
        for (int i = 0; i < m; i++) {
            for (int j = i-1; j >= 0; j--) {
                if (nums[i] > nums[j]) {
                    // 能跟在j后面
                    if (len[j] + 1 == len[i]) {
                        // 说明此时又找到一个相同最长的
                        // 因为第一次找到一个最长的时候已经更新了len,所以这里不需要更新也行
                        count[i] += count[j];
                    }
                    if (len[j] + 1 > len[i]) {
                        // 说明此时找到一个更长的
                        len[i] = len[j] + 1;
                        count[i] = count[j];
                    }
                    // 对于<直接忽略
                }
            }
            // 循环结束之后,更新一下len和count
            if (retlen < len[i]) {
                // 小于则更新
                retlen = len[i];
                retcount = count[i];
            }
            else if (retlen == len[i]) {
                retcount += count[i];
            }
        }
        return retcount;
    }
};

最长数对链

646. 最长数对链 - 力扣(LeetCode)

本题其实很简单,只是之前学的是单个数,这里是两个数字,但是比较也是一个数,因为每个数对严格递增

本题作者没有读清楚题目,想当然的认为是一个升序,但是这里特殊在于示例二,有可能先链接后面再链接前面,也就是每一组的选择是随机的

以下就是错误的写法

本质原因是因为你在以i位置结尾的时候,跟之前的子数组和子序列一样,当前状态只依赖之前的状态,也就是你以i位置为结尾的所有子序列只跟i-1,i-2有关

如果你这里没有预处理,就会导致你填写i位置的时候,即跟i-1,i-2有关,又跟i+1,i+2有关

那么我们这里想一想是否可以排个序,做个预处理

当然可以,假设你排完序之后是[a,b][c,d],ab填写的时候肯定用不到cd,即使a和c相等,那你a压根链接不了在d后面

这样排完序之后,所有当前状态的填写仅仅依赖于前面的状态

class Solution {
public:
    int findLongestChain(vector<vector<int>>& pairs) {
        //预处理,排序一下
         sort(pairs.begin(), pairs.end(), [](const vector<int>& a, const vector<int>& b) {
            if (a[0] == b[0]) return a[1] < b[1];
            return a[0] < b[0];
        });
        int m=pairs.size();
        vector<int> dp(m,1);
        for(int i=0;i<m;i++){
            for(int j=i-1;j>=0;j--){
                if(pairs[i][0]>pairs[j][1]){
                    //大于则跟在后面
                    dp[i]=max(dp[j]+1,dp[i]);
                }
            }
        }
        //返回dp中最大的
        int ret=0;
        for(int i=0;i<m;i++){
            ret=max(ret,dp[i]);
        }
        return ret;
    }
};

最长定差子序列

1218. 最长定差子序列 - 力扣(LeetCode)

这一看就是简单题,跟之前一样,定义dp[i]

向前遍历找到最长的符合定差的子序列

初始化:全部为1,因为至少都有一个

填表顺序:从左往右

返回值:返回dp表当中最大的,因为其他的不会被改变,只能为1,定差的要么为1要么很大

但是超时,此时要想办法去优化

优化点1:直接找到第一个符合的dp[j]就退出循环,但是这个也超时了

优化点2:将元素和dp[j]绑定存入hash表中,并且直接在hash表当中做动态规划

此时初始化为hash[arr[0]]=1,意思就是hash[元素]=1

class Solution {
public:
    int longestSubsequence(vector<int>& arr, int difference) {
        int m = arr.size();
        unordered_map<int, int> hash;
        hash[arr[0]]=1;
        int ret = 1;
        for (int i = 1; i < m; i++) {
            int val = hash[arr[i] - difference];//找到对应的元素的dp[j]
            hash[arr[i]] = val + 1;
            ret = max(ret, hash[arr[i]]);
        }
        return ret;
    }
};

注意如果你使用int hash[],需要开20000的空间,因为可能arr[i]为负数,如果映射进去会越界

这种写法会保证hash[arr[i]]至少是1,而且你从左往右填表

也能保证是最后一个元素,因为你是从左往右填表,会就导致右边的覆盖左边的hash,所以你找到的元素是最后一个元素

然后初始化要初始化第一个元素,不然第一个找不到前驱会出错

时间复杂度O(N),空间复杂度O(1)

最长的斐波那契子序列的长度

873. 最长的斐波那契子序列的长度 - 力扣(LeetCode)

如果按照之前的以某个位置为结尾去推导,是推导不出状态转移方程的

对于dp[i]和dp[j],你可以用nums[i]-nums[j]找到第一个元素,这样就可以组成斐波那契子序列,但是你不能用dp[j]的值去更新dp[i],因为你压根不知道dp[j]结尾的最长斐波那契子序列长啥样,不一定是nums[i]-nums[j],nums[j],nums[i]这个序列

也可能是a,nums[j]   b,nums[j]

根本原因是你无法通过dp[j]这个状态知道以j位置为结尾的最长斐波那契子序列长啥样,你不知道i位置能否和j位置匹配上

解决方案:将两个元素固定为结尾,找前一个元素,因为我们可以通过a,b  推导出前一个元素为b-a , a, b

接着推导出  a-(b-a) , b-a , a , b

并且很明显感受到你以两个位置为结尾,只需要像之前做的题目一样,找到前一个符合的

这里为什么不能像之前,是因为你以一个结尾时,要找到前两个符合的

所以我们把两个结尾放后面

dp[i][j]:以i位置j位置结尾的最长的斐波那契子序列的长度(规定i<j)

这里a要分三种情况

1:如果a存在,并且在ij前面

2:如果a存在,a在i和j之间

3:如果a不存在

并且这里注意更新为2是他们本身的长度至少两个元素,不然你后面更新+1的时候会出错

优化点由于题目严格递增,所以hash表当中,元素和下标是唯一的

初始化的时候是更新表当中的值为2,因为你是规定i<j的,也就是你只会用到表上方的,表下方和对角线用不到

class Solution {
public:
    int lenLongestFibSubseq(vector<int>& arr) {
        int m = arr.size();
        // 预处理,把元素和下标绑定
        unordered_map<int, int> hash;
        for (int i = 0; i < m; i++) {
            hash[arr[i]] = i;
        }
        int ret = 2;
        vector<vector<int>> dp(m, vector<int>(m, 2));
        for (int j = 2; j < m; j++) {
            for (int i = 1; i < j; i++) {
                if (hash.count(arr[j] - arr[i]) && hash[arr[j] - arr[i]] < i) {
                    // 存在且小于i
                    dp[i][j] = dp[hash[arr[j] - arr[i]]][i] + 1;
                }
                // 其余情况不管,本身初始化已为2了
                ret = max(ret, dp[i][j]);
            }
        }
        return ret < 3 ? 0 : ret;
    }
};

最长等差数列

1027. 最长等差数列 - 力扣(LeetCode)

一开始尝试dp[i]能否推出状态转移方程,这和之前一样无法推出,因为你以i位置结尾的时候,是无法知道你前面位置的状态,你无法知道能否组成最长等差数列,有时候可以知道等差,但你无法知道最长,你不知道前面那个状态的最长的等差数列的公差是多少

你必须知道一个等差数列的最后两项才能推出这个等差数列

也就是你得知道i位置和i位置前面某个位置的最长的等差数列

换言之,你得以两个位置为结尾才能表示一个等差数列的信息

本题不能像之前一样,采用hash来表示元素-下标对应关系,因为这里题目没有说明是严格递增或者说明每个元素只出现一次,否则hash当中会出现后元素覆盖前一个元素的情况

状态转移方程其实跟之前的一样的

注意当a存在,k<i的时候,假设有很多个符合的a,那只需要取最接近i的那个即可,因为这个的情况可能是最长的

如果不使用hash,那找下标k的时候时间复杂度会多一层O(N),导致最后是O(n^3)

所以要想办法优化

优化点来说:如果还想要沿用hash表,那就得创建unordered_map<int,vector<int>>,因为你同一个元素的下标不能覆盖,所以要创建一个下标数组,但是当特殊情况的时候,你的下标数组非常大,就会导致还需要去下标数组当中遍历一遍,时间复杂度仍然O(n^3)

优化二:一边dp一边保存最近的元素的下标,这和填表顺序有关,看下面

初始化:全部为2,因为严格定义i<j,所以对角线和下方dp表不需要

填表中dp[i][j]=dp[k][i]+1,我们仅仅需要保证填表时从上往下填表即可

这样就衍生出两种填表方式

为了匹配优化点2,我们需要采用第二种填表

第二种填表的意思时,你固定i位置,去跑j位置,那此时i之前的所有就可以用hash去存,不用担心覆盖,因为覆盖时,他会采用离i位置最近的去覆盖

如果你采用第一种,那你每次跑i位置的时候,里你最近的元素会动态变换

所以本质就是因为你需要离你最近的元素的下标,当你固定i时,最近的元素的下标也固定了

当你i跑动的时候,最近元素的下标也会随i跑动变来变去

class Solution {
public:
    int longestArithSeqLength(vector<int>& nums) {
        unordered_map<int, int> hash; // 元素值和下标的映射关系
        hash[nums[0]] = 0;
        int m = nums.size();
        if (m <= 2) {
            return m;
        }
        vector<vector<int>> dp(m, vector<int>(m, 2));
        int ret = 2;
        for (int i = 1; i < m; i++) {
            // 固定倒数第二个数
            for (int j = i + 1; j < m; j++) {
                // 固定倒数第一个数
                int a = 2 * nums[i] - nums[j]; // 要找的k
                if (hash.count(a)) {
                    // k存在并且k小于i
                    dp[i][j] = dp[hash[a]][i] + 1;
                }
                // 不存在或k>i都不用管
                // 每次处理完更新ret
                ret = max(ret, dp[i][j]);
            }
            // 更新hash
            hash[nums[i]] = i;
        }
        return ret;
    }
};

这里不需要判断k下标是不是在i的左边,是因为你hash当中定义存储的是离i位置最近的元素,并且都是在左边

等差数列划分Ⅱ

446. 等差数列划分 II - 子序列 - 力扣(LeetCode)

根据前面做的这么多道题

直接以i位置为结尾肯定不行,因为你不能采用单单一个i位置的元素去确定前面的等差数列

所以这里以i j为结尾

dp[i][j]:表示以i j位置为结尾的所有等差数列的个数

状态转移方程:a==2b-c

跟之前一样分a存不存在,下标k在哪里

当a存在且k<i时,dp[i][j]=  dp[k][i]==0?1:dp[k][i]+1

0的意思是一开始如果是两个元素我们需要初始化为0,当来第三个元素时我们需要判断一下之前是不是两个元素也就是0,如果是,那此时dp[i][j]=1

也有可能之前已经有多个等差数列了,那此时在加一个元素进来,应该要在之前的基础上+1

当a不存在或者存在k不符合时,dp[i][j]=0即可

注意:需要三次循环,因为我们要找到每个以k i为结尾的等差数列,然后相加

初始化:全部为0即可

填表顺序:从上往下

返回值:dp表当中的总和,因为一开始初始化为0,所以不影响

class Solution {
public:
    int numberOfArithmeticSlices(vector<int>& nums) {
        int m = nums.size();
        vector<vector<int>> dp(m, vector<int>(m, 0));
        int ret = 0;
        for (int j = 2; j < m; j++) {
            for (int i = j - 1; i >= 0; i--) {
                for (int k = i - 1; k >= 0; k--) {
                    long long tmp = 2 * (long long)nums[i] - (long long)nums[j];
                    if (tmp == (long long)nums[k]) {
                        if (dp[k][i] == 0) {
                            dp[i][j] += 1;
                        } else {
                            dp[i][j] += dp[k][i];
                            dp[i][j] += 1;
                        }
                    }
                }
                ret += dp[i][j];
            }
        }
        return ret;
    }
};

很明显时间复杂度O(n^3)

想办法优化一下:是否可以将所有以计算出来的a==2b-c

这个a存入到hash中呢?这样就不需要每次固定i和j,往前遍历k了

很明显可以,hash当中存储<元素值,下标数组>

class Solution {
public:
    int numberOfArithmeticSlices(vector<int>& nums) {
        int m = nums.size();
        unordered_map<long long, vector<int>> hash;
        for (int i = 0; i < m; i++) {
            hash[nums[i]].push_back(i);
        }
        vector<vector<long long>> dp(m, vector<long long>(m, 0));
        int ret = 0;
        for (int j = 2; j < m; j++) {
            for (int i = j - 1; i >= 0; i--) {
                long long tmp = 2 * (long long)nums[i] - (long long)nums[j];
                if (hash.count(tmp)) {
                    for (auto k : hash[tmp]) {
                        if (k < i) {
                            dp[i][j] += dp[k][i] + 1;
                        }
                    }
                }
                ret+=dp[i][j];
            }
        }
        return ret;
    }
};

总结

做了这么多道题

很明显感受到子序列不连续而导致结果非常多

而且有些不能以某个位置为结尾,因为你只能判断个数或者长度,无法知道前面的子序列长什么样

所以我们有些题需要以i j位置为结尾

有时候你不优化,时间复杂度为O(n^3),因为你需要固定i 和 j,还要往前遍历k寻找结果

核心特征是子序列不要求连续,但保持元素原有顺序,与 “子数组 / 子串(连续)” 形成关键区别。以下从核心分类、解题框架、经典题型、避坑要点四个维度,结合你刷过的题目做系统总结:

一、子序列题型核心分类(按考点)

1. 最长类子序列(求最大长度)

核心特征

目标是找到满足特定条件的最长子序列,是子序列题型的基础。

经典题型 & 解题框架

表格

题型

核心解法

关键思路

最长递增子序列(LIS)

动态规划 O (n²)/ 二分 O (n)

dp [i] = 以 nums [i] 结尾的最长递增子序列长度;二分优化需维护有序数组

最长定差子序列

哈希优化 DP O (n)

hash [num] = 以 num 结尾的最长定差子序列长度,转移:hash [num] = hash [num-d]+1

最长等差数列

哈希优化二维 DP O (n²)

dp [j][d] = 以 j 结尾、公差 d 的最长等差数列长度,转移:dp [j][d] = dp [i][d]+1

核心规律

  • 单条件约束(如递增、定差):优先一维 DP + 哈希优化(避免 O (n²));

  • 多条件约束(如等差数列,需公差):二维 DP(维度为 “结尾位置 + 约束条件”)。

2. 计数类子序列(求符合条件的个数)

核心特征

统计满足条件的子序列总数(常要求长度≥k),易出现 “重复计数”“漏计数” 问题。

经典题型 & 解题框架

表格

题型

核心解法

关键思路

等差数列划分 II - 子序列

哈希优化二维 DP O (n²)

dp [j][d] = 以 j 结尾、公差 d 的子序列个数(长度≥2),结果累加 dp [i][d](长度≥3)

不同的子序列(字符串)

动态规划 O (n*m)

dp [i][j] = 前 i 个字符包含前 j 个模式串的子序列数,注意去重(重复字符)

核心规律

  • 避免重复计数:按 “结尾位置 + 约束条件(如公差)” 分组统计,而非暴力遍历所有子序列;

  • 长度≥k 的统计:先统计长度≥2 的基础数,再剔除不符合长度的部分(如等差数列只累加 dp [i][d]);

  • 溢出问题:涉及大数时用 long long,避免 int 溢出(如公差计算、计数累加)。

3. 存在性类子序列(判断是否存在)

核心特征

判断是否存在满足条件的子序列(如是否存在和为 k 的子序列、是否为斐波那契子序列)。

解题框架

  • 暴力 DFS(小数据量):枚举所有可能的子序列,判断条件;

  • 动态规划(大数据量):dp [i] 记录前 i 个元素是否能满足条件(如和为 k);

  • 哈希辅助:快速查找是否存在满足条件的前驱元素(如斐波那契子序列需找 nums [i]-nums [j] 是否存在)。

4. 构造类子序列(构造符合条件的子序列)

核心特征

构造满足条件的最优子序列(如最大字典序子序列、最短子序列)。

解题框架

  • 贪心 + 栈(字符串类):如 “移掉 k 位数字”“最大子序列”,按贪心规则维护栈;

  • 双指针:如 “删除最短子数组使剩余有序”,找前后有序区间的衔接点。

二、子序列题型通用解题技巧

1. 动态规划是核心(90% 的子序列题)

  • 状态定义技巧:优先定义 “以 nums [i] 结尾” 的子序列状态(如 dp [i]),而非 “前 i 个元素”,减少状态冗余;

  • 转移方程技巧:

    • 单约束:dp [i] = 基于前驱元素的 dp [j] + 1(如最长递增子序列);

    • 多约束:dp [i][c] = 基于前驱的 dp [j][c'] + 1(c 为约束条件,如公差);

  • 优化技巧:哈希表替代前驱遍历(如定差 / 等差数列题),将 O (n²)→O (n)。

2. 避免暴力枚举(子序列总数是 2ⁿ,必超时)

  • 暴力枚举仅适用于 n≤20 的场景(可回溯),n≥100 时必须用 DP / 哈希优化;

  • 计数类题目:绝不能遍历所有子序列再判断,需按 “分组统计” 思路(如按公差分组)。

3. 关键细节避坑

  • 下标越界:子序列要求元素顺序,前驱下标必须小于当前下标(如 k<i<j),需严格判断;

  • 重复元素处理:

    • 最长类:哈希表存储 “值→所有下标”,而非最后一个下标(避免覆盖);

    • 计数类:按约束条件分组(如公差),避免同一子序列被多次统计;

  • 数据类型:涉及大数计算(如公差、计数)时,用 long long 替代 int(如 2*nums [i] 易溢出);

  • 边界条件:

    • 数组长度 < 2/3 时,直接返回长度 / 0(如等差数列题,长度 < 3 无符合条件的子序列);

    • 空序列 / 单元素序列:单独处理,避免 DP 初始化错误。

三、刷过的题目核心回顾

表格

题目

题型分类

核心错误 & 修复要点

longestSubsequence(定差)

最长类

哈希覆盖重复值下标→存储所有下标;负数下标→map / 偏移量

longestArithSeqLength(等差)

最长类

哈希覆盖→存储下标列表;k≥i 导致越界→严格判断 k<i + 二分查找

numberOfArithmeticSlices(计数)

计数类

三维循环超时→哈希优化 O (n²);重复计数→按公差分组;漏加 1→dp [j][d] += dp [i][d]+1

四、总结关键点

  1. 核心思想:子序列的核心是 “顺序不变 + 非连续”,解题优先抓 “结尾位置 + 约束条件”,用 DP + 哈希优化避免暴力;

  2. 最长类:状态定义为 “以 i 结尾的最长长度”,转移依赖前驱的符合条件状态;

  3. 计数类:状态定义为 “以 i 结尾的个数”,结果累加需剔除长度不足的部分,避免重复;

  4. 避坑重点:哈希表存储所有下标(防覆盖)、严格判断下标顺序(防越界)、用 long long 防溢出。

掌握以上框架,能解决 80% 以上的子序列题型,剩余 20%(如构造类)需结合贪心 / 栈等特殊技巧,核心仍是 “按约束条件分组,避免暴力枚举”。

[DP解题] 乘积最大子序列 [DP解题] 乘积最大子序列 原题链接:https://leetcode.com/problems/maximum-product-subarray/ [问题] Given an integer arraynums, find the contiguous subarray within an array (containing at least one number) which ha... 阅读详情

相关推荐

dp上升子序列方案数

P3970 [TJOI2014]上升子序列 题目描述 给定一个只包含整数的序列(序列元素的绝对值大小不超过10^9),你需要计算上升子序列的个数,满足如下条件的称之为一个上升子序列: 是原序列的一个子序列 长度至少为2 所有元素都严格递增 如果两个上升子序列相同,那么只需要计算一次。例如:序列{1,2,3,3}有4个上升子序列,分别为{1,2}{1,3},{1,2,3},{2,3}...

咸鱼 871

DP:子序列模型

子数组vs子数列 1、子数组(n^2) 子序列(2^n) 2、子数组是子序列的一个子集 3、子数组必须连续,子序列可以不连续

weixin_51142926的博客 2149

【WILEY】从Revision到Online:逐层拆解投稿状态更新与关键文件提交实战

本文详细解析了学术论文从返修到在线发表的全过程,重点拆解了投稿系统状态更新背后的含义与应对策略。文章提供了返修稿与回复信的准备技巧、关键补充文件(如图文摘要)的提交要点,并分享了校对校样与处理出版协议的实战经验,帮助作者高效、专业地完成发表流程。

weixin_29209805的博客 713

DP:子序列问题

在计算机科学和数学中,子序列(Subsequence)是指从一个序列中删除一些元素(可以是零个或多个),但不改变其余元素相对顺序后形成的新序列。子序列的特点元素的相对顺序保持不变。可以删除零个或多个元素。一个序列的子序列可以为空序列,即不包含任何元素。举例说明设有序列 S = [A, B, C, D, E],则其子序列可以有:删除零个元素:[A, B, C, D, E](即自身)

2301_79969994的博客 3359

Innovus ECO技巧:用setEcoMode+ecoAddRepeater实现批量buffer替换的5个关键步骤

本文详细介绍了在Cadence Innovus工具中,利用setEcoMode与ecoAddRepeater命令实现高效、批量Buffer/Inverter替换的工程化策略。通过启用批处理模式、构建可维护的替换规则、掌握高级定位技巧以及规避布线陷阱等5个关键步骤,帮助芯片设计工程师在ECO阶段大幅提升修改效率与质量,确保设计稳定签核。

d6e7f8的博客 1117

手把手教你搞定EMC EFT测试:从电源线到信号线的完整整改实战(附常见失败原因)

本文详细解析了EMC EFT测试中的常见问题及整改措施,从电源线到信号线的完整实战方案。通过四步定位法和成本最优整改策略,帮助工程师有效应对电快速脉冲群干扰,提升产品通过率并降低成本。

weixin_30920597的博客 498

[算法] DP-被3整除的子序列

原题连接:https://ac.nowcoder.com/acm/skill/detail/acm/1301 这道题挺有意思的,不过值得注意到的一点是这里的子序列并非是连续的子串,而且子序列是有顺序的但不一定要连续,例如示例3中的333(粗体代表第一个3,正常体代表第二个,斜体代表第三个) 所得到的7种子序列为:3、3、3、33、33、33、333。 用到动态规划,题目可以理解为前n个(包括n)...

Orion_lin的博客 2119

使用Google Earth Engine(GEE)的Python API,您可以将经纬度坐标转换为Landsat影像的路径/行号

使用Google Earth Engine(GEE)的Python API,您可以将经纬度坐标转换为Landsat影像的路径/行号。在这个示例中,我们选择了一个位于加利福尼亚州旧金山市附近的点,其经度为-122.084,纬度为37.421998。首先,我们需要导入必要的库和模块。接下来,我们定义一个函数,该函数将接收经度和纬度作为输入,并返回对应的Landsat路径/行号。最后,我们提取路径和行号的值,并将它们作为结果返回。现在,我们可以使用这个函数来获取特定点的Landsat路径/行号。

VwajAlgorithm的博客 348

线性DP LIS 最长上升子序列

为:读入a[i],从1到n利用循环记录dp[i],如果当前的a[i]大于a[j],那就意味着a[i]可以加入到以a[j]结尾的子序列中,在这种情况下我们可以更新dp[i]的值,取dp[i]和dp[j]+1的最大值(加1表示在a[j]的基础上添加a[i]至末尾)。:f[i]代表以a[i]为终点的最长上升子序列,g[i]代表以a[i]为起点的最长下降子序列,因为在f[i]和g[i]都计算了一次a[i],所以要减去1,求和-1之后的数量就是以某个元素为转折点的先上升后下降的长度。例如1 2 4 1 3 4。

2301_80170790的博客 1429

微信公众平台 - 微信内置浏览器 WeixinJSBridge.invoke(‘xxx‘) API 报错:system:access_denied(常见于微信支付、分享朋友圈/转发好友等功能)解决方案

微信支付提示System:access_denied,调用微信支付返回:system:access_denied为什么,安卓版微信支付偶尔出现system:access_denied,微信公众号开发分享出现system:access_denied,system:access_denied如何解决?微信WeixinJSBridge.invoke(‘xxx‘)API调用后出现报错,微信内部H5WeixinJSBridge.invokesystem:access_denied,使用微信jssdk时报ac

🏆 前端领域优质创作者 5145

096、PCIE环回模式(Loopback):调试时自己和自己对话的利器

摘要: PCIE环回模式是调试利器,通过让芯片内部数据自环来定位问题层级。文章详解三种环回模式:物理层环回验证基础收发,数据链路层环回测试DLLP机制,事务层环回验证端到端协议。分享了实战中的典型问题(时钟异常、状态机卡死、计数器失真)和"从下往上"的调试流程,强调要区分调试与生产功能,建议早期验证环回模式。通过具体代码示例和调试案例,说明合理使用环回模式能显著提升排查效率,是PCIE调试必备技能。(149字)

z534437748的博客 105

动态规划(DP)---- 子序列

在做动态规划的相关题,我们会遇到最长公共子序列,最长递增子序列,最长递增公共子序列等相关类型题,那么本期内容将围绕其展开讨论。

记得开心一点嘛的博客 1239

任务混部最少服务器数量计算问题

这里的并行度指的是该任务运行时会占用的服务器数量。要注意的是,一个服务器在每个时刻可以被任意任务使用,但同一时刻最多只能被一个任务占用,并且任务运行结束后会立即释放服务器(结束时刻不占用)。最后,我们从字典中找出服务器数量的最大值,这个最大值就是完成这批任务混部最少需要的服务器数量。对于每个任务,我们会遍历其对应的时间区间,将该任务的并行度累加到对应时刻的服务器数量上。任务混部的目标是让这批任务由同一批服务器承载运行,你需要计算完成这批任务混部最少需要多少服务器,以此来最大化控制资源成本。

m0_62153576的博客 106

fzu2129 子序列个数(计数dp)

题目 给n(n<=1e6)个数,统计所有不同子序列的个数 第i个数个数ai(0<=ai<=1e6),答案mod 1e9+7 思路来源 https://blog.youkuaiyun.com/icefox_zhx/article/details/77254400 题解 last[v]表示v上一次出现的位置 dp[i]表示到i为止,所有本质不同子序列的个数 考虑a[i], ①如...

小衣同学的博客 1352

解决Gradle中NPM命令失效问题

摘要:本文分析了IntelliJ IDEA中Gradle执行npm命令失败的问题,报错显示"npm"文件不存在。通过确认npm安装路径、检查系统PATH与Gradle环境的差异,提出两种解决方案:1) 在build.gradle中使用npm绝对路径;2) 通过launchctl修改用户PATH。实例展示了如何修改Gradle脚本或系统配置来解决该问题,指出跨平台开发中环境变量配置的重要性。文章为开发者提供了处理类似问题的实用方法。

2501_90323865的博客 90

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

被3整除的子序列DP

链接:https://ac.nowcoder.com/acm/problem/21302 来源:牛客网 给你一个长度为50的数字串,问你有多少个子序列构成的数字可以被3整除 答案对1e9+7取模 输入描述: 输入一个字符串,由数字构成,长度小于等于50 输出描述: 输出一个整数 思路:这道题的转移很有意思,设dp[i][k] 表示长度为 i 的数字串中模 3 等于 k 的子序列...

zhiyeegao的博客 9357

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

P1410 子序列(DP)

P1410 子序列 思路:看到nnn最大是200020002000。就应该想到用dpdpdp写写。根据别人大佬的思路,我再来说说。。。首先我们可以假设dp[i][j]dp[i][j]dp[i][j]为前iii位以a[i]a[i]a[i]结尾子序列长度为jjj是否满足。但是我们用这一个递推是不能判断另外一个子序列是否满足的,因此我们需要用dp[i][j]dp[i][j]dp[i][j]来表示前iii位不以a[i]a[i]a[i]结尾长度为i−ji-ji−j的最后一位的最小值(根据贪心越小越有利后面的数)。这样

Harris-H的博客 767

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

牛客 被3整除的子序列dp

题意很清楚,直接dp即可,dp[i][j]表示到第i个字符的状态为j的方案数,这里状态指的是到第i直接dp即可,dp[i][j]表示到第i个字符的状态为j的方案数,这里状态指的是到第i直接dp即可,dp[i][j]表示到第i个字符的状态为j的方案数,这里状态指的是到第i个字符数字和取模3的值,分情况转移一下即可.数字和取模3的值,分情况转移一下即可.数字和取模3的值,分情况转移一下即可. #inc...

pubgoso的博客 350

最长公共上升子序列(DP)

问题描述:在动态规划中,最长公共子序列(LCS)与最长上升子序列(LIS)是基础的线性DP。现在要求求出两个不等长序列的最长公共上升子序列,故名思意,定义类似前两个问题。 解题思路:如果已经会求最大公共子序列,那么自然就可以想到,只需在最长公共子序列上找一遍最长上升子序列即可。然而具体的状态转移方程,只要在两个元素判等后进行进一步操作即可,稍微注意一下状态的始末,即k为何从0开始而不是1。该方法...

帅比王的博客 8715

最长公共子序列(线性dp)-java

本文主要来描述两个字符串的最长公共子序列问题

m0_63267251的博客 1728

[dp10_子序列] 最长递增子序列 | 摆动序列 | 最长递增子序列的个数

子序列--两层for循环判断 满足某一条件,就填表取最大

No Game No Life 1101

被3整除的子序列(线性dp)

时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 524288K,其他语言1048576K 64bit IO Format: %lld 题目描述 给你一个长度为50的数字串,问你有多少个子序列构成的数字可以被3整除 答案对1e9+7取模 输入描述: 输入一个字符串,由数字构成,长度小于等于50 输出描述: 输出一个整数 示例1 输入 复制 132 输出 复制 3 示...

Kruskal的博客 2467

【Day38 LeetCode】动态规划DP 子序列问题Ⅱ

确定dp数组含义,dp[i][j]表示长度为[0, i - 1]的字符串text1与长度为[0, j - 1]的字符串text2的最长公共子序列的长度。dp转移关系,对于当前值dp[i][j], 分为text1[i - 1] 与 text2[j - 1]相同与不相同两种情况。text1[i - 1] 与 text2[j - 1]相同时,这两个字符可以作为最长公共子序列的一部分,dpijdpi−1j−11;

H2020fighting的博客 1295

ABC271 dp构造方案 子序列dp 折半搜索

一个牌序列,每张牌正反面都有数字,只能选一面,问最终选出来的元素和为s的方案?本身是个类似背包dp,要求方案的话,记录一下每个位置,和为一个值的最后一次牌,是正面还是反面,从dp终止状态倒退就能得到一个方案需要注意方案也是倒着来的,需要反过来输出。

Maxwell_Newton的博客 721

dp训练1.被3整除的子序列

来源:牛客网题目描述给你一个长度为50的数字串,问你有多少个子序列构成的数字可以被3整除答案对1e9+7取模输入一个字符串,由数字构成,长度小于等于50输出一个整数132132339911333333771234561234562323000033n为长度子任务1: n <= 5子任务2: n <= 20子任务3: 无限制对题目进行理解,首先对3取余的结果就三种:0,1,2。

BryceBorder的博客 904

DP进阶之最长递增子序列

其实我们思考一下,我们只需要知道,在数组nums下标为i的时候,nums[i] 与前面nums[j] (j<i)的比较,如果大于,则可以知道前面肯定有递增子序列,dp[j] + 1就是它与j位置的子序列的长度,再与自己之前的比较,看看哪个最大取哪个。2、我们可以用动态规划来解决,通过找到数组中第i个位置时候最长严格递增子序列的长度,求出最长的长度。1、首先来阅读题目,题目的意思是说,要我们求在一个数组中,来找一组从小到大递增的序列。最长递增子序列是 [2,3,7,101],因此长度为 4。

MADAO的博客 380
上一篇: 动态规划核心:数组问题实战
下一篇: 回文子串与子序列dp全解析
Fcw0551
博客等级 码龄2年 330粉丝 115原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值