当前位置: 首页 > news >正文

【动态规划】子序列问题

个人主页 : zxctscl
专栏 【C++】、 【C语言】、 【Linux】、 【数据结构】、 【算法】
如有转载请先通知

文章目录

  • 前言
  • 1 ==300. 最长递增子序列(经典)==
    • 3.1 分析
    • 3.2 代码
  • 2 376. 摆动序列
    • 2.1 分析
    • 2.2 代码
  • 3 673. 最长递增子序列的个数
    • 3.1 分析
    • 3.2 代码
  • 4 646. 最长数对链
    • 4.1 分析
    • 4.2 代码
  • 5 1218. 最长定差子序列
    • 5.1 分析
    • 5.2 代码
  • 6 873. 最长的斐波那契子序列的长度
    • 6.1 分析
    • 6.2 代码
  • 7 1027. 最长等差数列
    • 7.1 分析
    • 7.2 代码
  • 8 446. 等差数列划分 II - 子序列
    • 8.1 分析
    • 8.2 代码

前言

在上一篇有关动态规划的博客中,谈到做这类题目的步骤,有需要的可以点这个链接: 【动态规划】斐波那契额数列模型。继续分享这个模型类型的题目。

1 300. 最长递增子序列(经典)

在这里插入图片描述
子序列中挑选的元素是可以不连续的,但是得保证子序列中元素的出现的相对顺序和原数组中是一致的
像[a,b,d]就是[a,b,c,d,e]的一个子序列,而[d,a,b]就不是。
在这里插入图片描述

3.1 分析

  1. 状态表示
    dp[i]表示:以i位置为结尾的所有子序列中,最长递增子序列的长度。

  2. 状态转移方程
    可以分两类,一类是就以i为子序列;
    第二类是跟前面元素一起构成子序列,但此时必须是前面的元素小于i,此时条件下再找最长子序列,要想和i组成最长,j也是最长子序列就是dp[j],再加上1就是最大的子序列。
    在这里插入图片描述

  3. 初始化
    把dp表里面所有值初始化为1,就不用考虑为1的情况了。

  4. 填表顺序

从左往右

  1. 返回值
    dp表里最大值
    在这里插入图片描述

3.2 代码

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

2 376. 摆动序列

在这里插入图片描述

2.1 分析

题目已知,仅有一个元素或者含两个不等元素的序列也视作摆动序列。
在这里插入图片描述

示例二中最长摆动序列长度是7

在这里插入图片描述

  1. 状态表示
    dp[i]表示:以i位置为结尾的所有的子序列中,最长的摆动序列的长度。
    最后一个位置可以分为是下降位置,也开始是上升位置:
    此时就细分为两个:
    g[i]表示:以i位置为结尾的所有的子序列中,最后一个位置呈现“下降”趋势的最长摆动序列的长度。
    f[i]表示:以i位置为结尾的所有的子序列中,最后一个位置呈现“上升”趋势的最长摆动序列的长度。

    在这里插入图片描述

  2. 状态转移方程
    f[i]可以分为,以i自己为一类,还可以分为和前面的i-1组合,此时将j设置为(0,i-1)
    f是呈现上升趋势的,此时i位置的值是大于i-1位置的,也就是说:nums[j]<nums[i],此时如果找到以j最后一个位置呈现“下降”趋势的最长的g[j]再加1,但是要的是一个最大值就是max(g[j]+1),f[i])。
    在这里插入图片描述

g[i]可以分为,以i自己为一类,还可以分为和前面的i-1组合,此时将j设置为(0,i-1)
g是呈现下降趋势的,此时i位置的值是大于i-1位置的,也就是说:nums[j]>nums[i],此时如果找到以j最后一个位置呈现“上升”趋势的最长的g[j]再加1,但是要的是一个最大值就是max(f[j]+1),g[i])。
在这里插入图片描述

  1. 初始化
    f表和g表全部初始化为1,就不用考虑长度为1的情况。

  2. 填表顺序
    从左往右,两个表一起填

  3. 返回值
    两个表里面的最大值
    在这里插入图片描述

2.2 代码

class Solution {
public:int wiggleMaxLength(vector<int>& nums) {int n=nums.size();vector<int> g(n,1),f(n,1);int ret=1;for(int i=1;i<n;i++){for(int j=0;j<i;j++){if(nums[i]>nums[j])f[i]=max(f[i],g[j]+1);else if(nums[i]<nums[j])g[i]=max(g[i],f[j]+1);}ret=max(g[i],f[i]);}return ret;}
};

3 673. 最长递增子序列的个数

在这里插入图片描述
如何一次遍历在数组中找到最大值出现的次数?
此时用到贪心,首先可以用两个变量,一个maxval用来记录当前扫描到数组中的最大值,另一个用来记录这个最大值出现的次数。
这时候就会出现三种情况:

在这里插入图片描述

3.1 分析

  1. 状态表示
    dp[i]表示:以i位置为结尾的所有子序列中,最长递增子序列的个数。

但是此时连最长的子序列多长都不知道,这个状态表示是不够的。

len[i]表示:以i位置为结尾的所有子序列中,最长递增子序列的“长度”。
count[i]表示:以i位置为结尾的所有子序列中,最长递增子序列的“个数”。

  1. 状态转移方程
    第一步以i位置单独为一个子序列就是1;
    第二步遍历[0,i-1]要形成递增子序列就必须是nums[j]<nums[i],要找到len[j]+1与len[i]中的最大值。
    在这里插入图片描述

既要找到最长的长度也得找到最长长度出现的次数。
单独一个序列长度就是1;
形成递增序列就必须是nums[j]<nums[i],此时会出现三种情况:(1)加上i位置能形成最长递增子序列,j元素为结尾能形成count[j]个递增子序列,此时总共就有count[i]+count[j]个递增子序列。(2)加上i位置能形成最长递增子序列,但跟在后面形成的子序列个数比len[i]少,此时就无视。(3)加上i位置能形成最长递增子序列长度会变成,此时就得更新最长的递增子序列,而且得重新计数,因为是用j位置来更新的,所以const[i]就等于count[j]。
在这里插入图片描述

  1. 初始化
    两个表都初始化为1

  2. 填表顺序
    从左往右

  3. 返回值
    贪心策略

在这里插入图片描述

3.2 代码

class Solution {
public:int findNumberOfLIS(vector<int>& nums) {int n=nums.size();vector<int> len(n,1),count(n,1);int retlen=1,retcount=1;for(int i=1;i<n;i++){for(int j=0;j<i;j++){if(nums[j]<nums[i]){if(len[j]+1==len[i])count[i]+=count[j];else if(len[j]+1>len[i])len[i]=len[j]+1,count[i]=count[j];}}if(retlen==len[i])retcount+=count[i];else if(retlen<len[i])retlen=len[i],retcount=count[i];}return retcount;}
};

4 646. 最长数对链

在这里插入图片描述

4.1 分析

要想得到这种形式的数对链,如果考虑以i位置为结尾,那么比i位置小的都在它前面,比它小的都在它后面,就得提前做一下数据的处理,把原数组按照第一个元素排序。
如果[a,b][c,d]是已经按照第一个元素排好序,那么c>=a,而在pair中左边元素始终小于右边元素,也就是说c<d,此时就能得出d>a,所以[a,b]是绝对不能连在[c,d]后面的。
排序就能保证以i位置为结尾的倒数第二个元素就一定在i位置左边。
在这里插入图片描述

  1. 状态表示
    dp[i]表示以i位置为结尾的所有的数对链中,最长的数对链的长度

  2. 状态转移方程
    如果就以i位置为结尾构成一个数对链,长度就是1,;
    如果i位置和前面位置一起构成数对链,就得满足前面i-1位置pair中右边元素dp[j][1]小于以i位置为结尾pair中左边元素dp[i][0],这时长度就是dp[j]+1,如果取里面最长的数对链:
    在这里插入图片描述

  3. 初始化
    把他们初始化为最差的长度,就都初始化为1

  4. 填表顺序
    从左往右

  5. 返回值
    返回里面的最大值
    在这里插入图片描述

4.2 代码

class Solution {
public:int findLongestChain(vector<vector<int>>& pairs) {sort(pairs.begin(),pairs.end());int n=pairs.size();vector<int> dp(n,1);int ret=1;for(int i=1;i<n;i++){for(int j=0;j<i;j++){if(pairs[j][1]<pairs[i][0]){dp[i]=max(dp[j]+1,dp[i]);}}ret=max(dp[i],ret);}return ret;}
};

5 1218. 最长定差子序列

在这里插入图片描述

5.1 分析

  1. 状态表示
    dp[i]表示以i位置的元素为结尾的所以子序列中,最长的等差子序列长度。

  2. 状态转移方程
    如果i位置里面的元素是a,倒数第二个元素是b,就是说a-b=diff,此时b=a-diff。
    将b分为两种情况:一种如果b不存在,那么只能是a单独构成子序列,长度就为1;另一种如果b存在,只考虑最后一个,因为b存在那么b前面的元素的值至少是大于或者等于b的值,此时长度就是dp[i]=dp[j]+1

在这里插入图片描述

将b的值和dp[j]绑定放在哈希表里,就不用再从前往后遍历,就能直接在哈希表里找到b和dp[j],能直接更新dp[i]。
直接在哈希表中做动态规划

  1. 初始化
    以0位置的元素为结尾的所以子序列中,长度就是1。
    hash[arr[0]]=1

  2. 填表顺序
    从左往右

  3. 返回值
    dp表里面的最大值
    在这里插入图片描述

5.2 代码

class Solution {
public:int longestSubsequence(vector<int>& arr, int difference) {//创建哈希表unordered_map<int,int>hash;//arr[i]-dp[i]hash[arr[0]]=1;int ret=1;for(int i=1;i<arr.size();i++){hash[arr[i]]=hash[arr[i]-difference]+1;ret=max(ret,hash[arr[i]]);}return ret;}
};

6 873. 最长的斐波那契子序列的长度

二维
在这里插入图片描述

6.1 分析

  1. 状态表示
    dp[i]表示:以i位置元素为结尾的所有子序列中,最长斐波那契子序列的长度。此时用这个状态表示,就只能知道斐波那契子序列的长度,但并不知道具体的斐波那契数列,所以这个状态表示是不行的。
    如果知道斐波那契数列最后面两个数a,b的值,就能知道斐波那契数列前面的数。
    在这里插入图片描述
    dp[i][j]表示以i位置以及j位置为结尾的所有子序列中,最长斐波那契子序列的长度。
    此时规定了i<j

  2. 状态转移方程
    假设j位置存放的是c,i位置值是b。
    此时设倒数第三个数下标是k,那么k位置的存放的值就是是c-b
    在这里插入图片描述
    分三种情况,(1)a存在,而且a<b,那么就能将k和i位置的值拿出来,再往前找前面能构成斐波那契数列的值,也就是dp[k][i],此时长度就是dp[k][i]+1
    (2)a存在,但是a在b c之间,不能构成斐波那契数列,但是此时里面有a b两个元素,所以里面的长度就是2
    (3)a不存在,就不能构成斐波那契数列,但是此时里面有两个元素,所以里面的长度就是2
    在这里插入图片描述

优化:
在做动态规划之前,先把值和它们下标绑定,就可以先存到哈希表中,就能直接找到下标

  1. 初始化
    把表里所有的值都初始为2

  2. 填表顺序
    从上往下

  3. 返回值
    返回dp表里面的最大值ret,如果里面没有最长斐波那契子序列,就返回0,否则就返回ret
    在这里插入图片描述

6.2 代码

class Solution {
public:int lenLongestFibSubseq(vector<int>& arr) {int n = arr.size();unordered_map<int, int>hash;for (int i = 0; i < n; i++){hash[arr[i]] = i;}int ret = 2;vector<vector<int>> dp(n, vector(n, 2));for (int j = 2; j < n; j++)//固定最后一个位置{for (int i = 1; i < j; i++)//固定倒数第二个位置{int a = arr[j] - arr[i];if (hash.count(a) && a < arr[i])dp[i][j] = dp[hash[a]][i] + 1;ret = max(ret, dp[i][j]);}}return ret < 3 ? 0 : ret;}
};

7 1027. 最长等差数列

在这里插入图片描述

7.1 分析

同上面一题类似

  1. 状态表示
    dp[i]表示:以i位置元素为结尾的所有子序列中,最长等差数列子序列的长度。此时用这个状态表示,就只能知道等差数列子序列的长度,但并不知道具体的等差数列的值,所以这个状态表示是不行的。
    如果知道等差数列最后面两个数a,b的值,就能知道等差数列前面的数。

在这里插入图片描述

dp[i][j]表示以i位置以及j位置为结尾的所有子序列中,最长等差数列子序列的长度。
此时规定了i<j

  1. 状态转移方程

分三种情况,(1)a存在,而且a<b,那么就能将k和i位置的值拿出来,再往前找前面能构成等差数列的值,也就是dp[k][i],此时长度就是dp[k][i]+1
(2)a存在,但是a在b c之间,不能构成等差数列,但是此时里面有a b两个元素,所以里面的长度就是2
(3)a不存在,就不能构成等差数列,但是此时里面有两个元素,所以里面的长度就是2

在这里插入图片描述

优化:
有两种方式:
(1)在做dp之前,先把值和它们下标绑定,就可以先存到哈希表中,<元素,下标组>
(2)一边dp,一边保存离他最近的下标元素的下标<元素,下标>
选择第二种方式填表,当i位置填完之后,将i位置的值放入哈希表中即可

  1. 初始化
    dp表里面初始化为2

  2. 填表顺序
    有两种填表顺序:
    (1)先固定最后一个数j,再枚举倒数第二个数,此时i是一直移动的
    (2)先固定倒数第二个数,再枚举最后一个数,这时候i和j同时向后移动一位
    在这里插入图片描述

  3. 返回值
    返回dp表中的最大值
    在这里插入图片描述

7.2 代码

class Solution {
public:int longestArithSeqLength(vector<int>& nums) {unordered_map<int,int> hash;hash[nums[0]]=0;int n = nums.size();vector<vector<int>> dp(n, vector<int>(n, 2));int ret = 2; for (int i = 1; i < n; i++)//固定倒数第二个位置{for (int j = i+1; j < n; j++)//枚举倒数第一个数{int a =  2*nums[i]-nums[j] ;if (hash.count(a))dp[i][j] = dp[hash[a]][i] + 1;ret = max(ret, dp[i][j]);}hash[nums[i]]=i;}return ret ;}};

8 446. 等差数列划分 II - 子序列

在这里插入图片描述

8.1 分析

同上面一题类似,只是这里要找到是等差数列的个数

  1. 状态表示
  2. dp[i]表示:以i位置元素为结尾的所有子序列中,等差数列子序列的个数。此时用这个状态表示,就只能知道等差数列子序列的个数,但并不知道具体的等差数列的值,所以这个状态表示是不行的。
    如果知道等差数列最后面两个数a,b的值,就能知道等差数列前面的数。

在这里插入图片描述
dp[i][j]表示以i位置以及j位置为结尾的所有子序列中,最长等差数列子序列的长度。
此时规定了i<j

  1. 状态转移方程

在这里插入图片描述

优化
在dp之前,将<元素,下标数组>绑定在一起,放在哈希表中

  1. 初始化
    dp表中值都初始化为0

  2. 填表顺序
    先固定倒数第一个数,再枚举倒数第二个数

  3. 返回值
    返回dp表里所有元素的和
    在这里插入图片描述

8.2 代码

class Solution {
public:int numberOfArithmeticSlices(vector<int>& nums) {int n = nums.size();unordered_map<long long,vector<int>> hash;for(int i=0;i<n;i++)hash[nums[i]].push_back(i);vector<vector<int>> dp(n, vector<int>(n));int sum = 0; for (int j = 2; j < n; j++)//固定倒数第一个位置{for (int i = 1; i< j; i++)//枚举倒数第二个数{long long a =  (long long)2*nums[i]-nums[j] ;if (hash.count(a)){for(auto k:hash[a]){if(k<i)dp[i][j]+=dp[k][i]+1;}}sum+=dp[i][j];}}return sum ;}
};

有问题请指出,大家一起进步!!!

相关文章:

  • Java 企业级开发设计模式全解析
  • 用户模块 - IP归属地功能实现与测试
  • AI Agent开发第50课-机器学习的基础-线性回归如何应用在商业场景中
  • PyTorch_自动微分模块
  • linux tar命令详解。压缩格式对比
  • C++访问MySQL
  • 联邦学习的深度解析,有望打破数据孤岛
  • 3.5/Q1,GBD数据库最新一区文章解读
  • rollout 是什么:机器学习(强化学习)领域
  • 【C/C++】各种概念联系及辨析
  • Socket 编程 TCP
  • 2025年PMP 学习五
  • Qt天气预报系统更新UI界面
  • 电路研究9.3.3——合宙Air780EP中的AT开发指南:HTTP(S)-HTTP GET 示例
  • 逆向常见题目—迷宫类题目
  • 【AI大模型学习路线】第一阶段之大模型开发基础——第四章(提示工程技术-1)In-context learning。
  • android-ndk开发(5): 编译运行 hello-world
  • 机器人强化学习入门学习笔记
  • EPSG:3857 和 EPSG:4326 的区别
  • 雷电模拟器-超好用的Windows安卓模拟器
  • 台湾关闭最后的核电,岛内担忧“非核家园”缺电、涨电价困局难解
  • 2025全球城市科技传播能力指数出炉,上海位列第六
  • 泽连斯基:俄代表团级别低,没人能做决定
  • 上海:到2027年,实现近海航线及重点海域5G网络高质量覆盖
  • 著名心血管病学专家李国庆教授逝世,享年63岁
  • 混乱的5天:俄乌和谈如何从充满希望走向“卡壳”