DP:子序列模型

news2024/10/5 17:08:27

子数组vs子数列

1、子数组(n^2)    子序列(2^n)       

2、子数组是子序列的一个子集

3、子数组必须连续,子序列可以不连续

一、最长递增子序列

. - 力扣(LeetCode)

算法原理:

1、状态表示(经验+题目要求)

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

 2、状态转移方程

(1)长度为1——>dp[i]=1

  (2)   长度大于1——>满足前提(nums[j]<nums[i])——>max(dp[j]+1,dp[i])  (0<=j<=i-1)

3、初始化

如果无法更新,最差情况自己也是一个子序列,所以dp表全都初始化为1

4、填表顺序

需要借助前面的状态,所以要从左往右

5、返回值

dp表中的最大值——>可以用ret去更新出最大值,也可以用*max_element(dp.begin(),dp.end())

6、复杂度

时间复杂度:N^2 (因为是子序列而非子数组,所以当我们固定住i的时候,他的前面可以是i-1、i-2、i-3…… 所以需要遍历一遍更新出最大的长度)

空间复杂度:N

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
         int n=nums.size();
         vector<int> dp(n,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]); //前提条件要满足
         return *max_element(dp.begin(),dp.end());//dp数组中的最大值
    }
};

二、摆动序列

. - 力扣(LeetCode)

算法原理:

1、状态表示(经验+题目要求)

dp[i]表示以i位置为结尾所有子序列中,最长递增子序列的长度。 (错误)

因为会存在两种状态,所以我们需要两个dp数组:

f[i]表示以i位置为结尾的所有子序列中,最后一个位置呈现“上升”趋势的最长摆动序列的长度

g[i]表示以i位置为结尾的所有子序列中,最后一个位置呈现“下降”趋势的最长摆动序列的长度

 2、状态转移方程

f[i](上升):

(1)长度为1——>1

  (2)   长度大于1——>满足前提(nums[j]<nums[i])——>max(g[j]+1,f[i])  (0<=j<=i-1)

g[i](下降):

(1)长度为1——>1

  (2)   长度大于1——>满足前提(nums[j]>nums[i])——>max(f[j]+1,g[i])  (0<=j<=i-1)

3、初始化

如果无法更新,最差情况自己也是一个子序列,所以g表和f表全都初始化为1

4、填表顺序

需要借助前面的状态,所以要从左往右,两个表一起填

5、返回值

两个表中的最大值

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        //f[i]表示以i位置结尾的最长子序列中,最后呈现上升趋势 (前提nums[i]<nums[j])
        //g[i]表示以i位置结尾的最长子序列中,最后成下降趋势    (前提nums[i]>nums[j])
        int n=nums.size();
        vector<int> f(n,1),g(n,1);
        for(int i=1;i<n;++i)
           for(int j=0;j<i;++j)
                if(nums[i]<nums[j])  g[i]=max(f[j]+1,g[i]);
                else if(nums[i]>nums[j]) f[i]=max(g[j]+1,f[i]);
        return max(*max_element(f.begin(),f.end()),*max_element(g.begin(),g.end()));
    }
};

三、最长递增子序列的个数

. - 力扣(LeetCode)

在讲解前先来个小demo:如何在数组中找出最大值出现的次数

方案1:第一次for循环确定最大的值是多少,第二次for循环统计最大的值出现了几次

方案2:利用贪心策略一次for循环搞定(定义maxval记录当前的最大值,count统计数量)

(1)x==maxval:++count 

(2)x<maxval:直接无视

(3)x>maxval:更新最大值——>maxval=x,然后重新计数——>count=1

算法原理:

1、状态表示(经验+题目要求)

dp[i]表示以i位置为结尾所有子序列中,最长递增子序列的个数。 (错误)

因为我们在填表的时候并不能确认最长递增子序列的长度是多少,所以无法直接统计。我们就得用demo中的方案2的思想,来解决这个问题。

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

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

 2、状态转移方程

nums[j]<nums[i]时

(1)len[j]+1==len[i]——>count[i]+=count[j]

(2)len[j]+1<len[i] 无视

(3)len[j]+1>len[i]——>len[i]=len[j]+1  count[i]=count[j](更新最大值并重新计数)

3、初始化

全都初始化为1

4、填表顺序

需要借助前面的状态,所以要从左往右,两个表一起填

5、返回值

recount统计结果

class Solution {
public:
    int findNumberOfLIS(vector<int>& nums) {
        int n=nums.size();
         vector<int> len(n,1),count(n,1); //count统计以i位置结尾时最长子序列的个数 len是长度
         int retlen=1,recount=1;//统计最大长度和最大长度的个数
         for(int i=1;i<n;++i)
         {
           for(int j=0;j<i;++j)
             if(nums[i]>nums[j]) //构成子序列的前提条件
                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]) recount+=count[i];
             else if(retlen<len[i])
             {
                retlen=len[i];
                recount=count[i];
             }
         }
         return recount;
    }
};

四、最长数链对

. - 力扣(LeetCode)

算法原理:

预处理:由于题目要求是任意顺序组成数链对,所以我们在处理的时候不仅要考虑前面,还要考虑后面,这样不利于我们的动态规划表示,所以我们要先按照第一个元素进行排序(比如[a,b] [c,d],a<c<d 所以d>a,所以后面的不需要考虑到),我们要进行sort,在C++中,vector、pair的默认比较逻辑都是按照字典序的,恰好符合我们的要求,所以我们可以直接调用sort。

1、状态表示(经验+题目要求)

dp[i]表示以i位置为结尾所有数对链序列中,最长数对链的长度。 

 2、状态转移方程

dp[i]:

(1)长度为1——1

(2)长度大于1——p[j][1]>p[i][0] ——max(dp[i],dp[j]+1)

3、初始化

初始化为1

4、填表顺序

需要借助前面的状态,所以要从左往右

5、返回值

要返回dp表中的最大值,但由于我们排序过,所以最大值必然出现在dp[n-1]。

class Solution {
public:
    int findLongestChain(vector<vector<int>>& pairs) {
        //vector的排序就像字典序一样
        //预处理直接sort 正好符合我们的要求
        sort(pairs.begin(),pairs.end());
        int n=pairs.size();
        vector<int> dp(n,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]);//(1,5)(2,3)(4,10)(5,9)
        return dp[n-1];//最大值必然在最后面
    }
};

五、最长定差子序列(经典)

. - 力扣(LeetCode)

算法原理:

1、状态表示(经验+题目要求)

dp[i]表示以i位置为结尾所有子序列中,最长的等差子序列长度 

 2、状态转移方程

dp[i]:

(1)b不存在——>1

(2)b存在——>取最后一个即可dp[j]+1

我们会发现前一个数基本上是可以确定是多少的,并且有多个的话也可以用后面的覆盖前面的,因此我们可以用哈希表做优化

优化思路:

(1)将元素+dp[i]的值存在哈希表中

(2)直接在哈希表中做动态规划

3、初始化

hash[arr[0]]=1

4、填表顺序

从左往右

5、返回值

dp表里的最大值

class Solution {
public:
    int longestSubsequence(vector<int>& arr, int difference) {
        //数据量太大了O(n^2)必然会超时
        int n=arr.size();
        unordered_map<int,int> hash;//第一个是元素,第二个是以这个元素为结尾时的最长等差子序列长度
        int ret=1;
        for(int&v:arr) //为了降低时间复杂度,我们发现这道题只需要最后的那个相同的
        {
            hash[v]=hash[v-difference]+1; //因为v-difference不在的时候,会被自己创建出来并初始化为0
            ret=max(ret,hash[v]);
        }
        return ret;
    }
};

    为什么哈希表不需要先将数组中的元素全部初始化为1???因为hash[v]=hash[v-difference]+1,当v-differences不存在的时候,重载方括号会去调用insert并允许我们修改second,在创建的时候初始化了。

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

. - 力扣(LeetCode)

算法原理:

1、状态表示(经验+题目要求)

dp[i]表示以i位置为结尾所有子序列中,最长的斐波那契子序列长度(错误)。

因为我们至少得确定两个位置,才能知道序列是否满足斐波那契子序列的要求。

dp[i][j]表示以i位置及j位置为结尾所有子序列中,最长的斐波那契子序列长度。

 2、状态转移方程

dp[i][j]:  (假设abc对应的坐标分别是kij)

(1)如果a存在且a<b——>dp[k][i]+1

(2)a存在且b<a<c——>2

(3)a不存在——>2

       我们固定两个数用了两层for循环了,如果找第三个数的时候还要在前面用一层for循环的话,那么就是n^3的时间复杂度了,所以我们可以用哈希表来帮助我们存储下标和元素的映射关系。并且我们只需要保存靠后的元素下标即可。

优化思路:将元素与下标绑定存放在哈希表中。

3、初始化

都初始化为2

4、填表顺序

从左往右

5、返回值

dp表里的最大值ret 但是如果ret是2的话就返回0

class Solution {
public:
    int lenLongestFibSubseq(vector<int>& arr) 
    {
         //必须通过元素快速找到dp表对应的下标 
         unordered_map<int,int> hash;//哈希帮助我们快速定位
         int n=arr.size();
         for(int i=0;i<n;++i) hash[arr[i]]=i;
         int ret=2;//起始为2
         vector<vector<int>> dp(n,vector<int>(n,2));//得知道两个位置,才能确定前面的
         for(int j=2;j<n;++j) //固定最后的位置
           for(int i=1;i<j;++i)//固定倒数第2个位置
            {
                int a=arr[j]-arr[i];
                if(hash.count(a)&&a<arr[i]) dp[i][j]=dp[hash[a]][i]+1;
                ret=max(dp[i][j],ret);
            }
        return ret==2?0:ret;
    }
};

七、最长等差数列

. - 力扣(LeetCode)

算法原理:

1、状态表示(经验+题目要求)

dp[i]表示以i位置为结尾所有子序列中,最长的等差子序列的长度(错误)。

因为我们至少得确定两个位置,才能知道序列是否满足等差子序列的要求。

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

 2、状态转移方程

dp[i][j]:  (假设abc对应的坐标分别是kij)

(1)如果a存在且a<b——>dp[k][i]+1

(2)a存在且b<a<c——>2

(3)a不存在——>2

       我们固定两个数用了两层for循环了,如果找第三个数的时候还要在前面用一层for循环的话,那么就是n^3的时间复杂度了,所以我们可以用哈希表来帮助我们存储下标和元素的映射关系。并且我们只需要保存靠后的元素下标即可。

优化思路:

(1)将元素与下标绑定存放在哈希表中。

(2)i位置填完后,将i位置的值放进哈希表中

3、初始化

都初始化为2

4、填表顺序

有两种方式(选择方法2

(1)先固定最后一个数,然后枚举倒数第二个数(必须在dp前先将元素与下标的关系绑定再哈希表中,到时候在找的时候还得判断b<a<c的情况)

(2)先固定倒数第二个数,再枚举最后一个数(可以等i位置填完后再将i的位置丢进哈希表,这样可以保证哈希表内的元素的下标必然是小的,就可以不需要判断b<a<c的情况)

5、返回值

dp表里的最大值ret

class Solution {
public:
    int longestArithSeqLength(vector<int>& nums) {
       //得确定至少两个位置,通过这两个位置求出了等差是多少,才能确定最终的结果
       unordered_map<int,int> hash;
       int n=nums.size();
       hash[nums[0]]=0;
       //必须要先固定两个数
       int ret=2;
       vector<vector<int>> dp(n,vector<int>(n,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(dp[i][j],ret);
         }
         hash[nums[i]]=i;//记住最后一个即可
       }
       return ret;
    }
};

八、等差数列划分II-子序列 

. - 力扣(LeetCode)

算法原理:

1、状态表示(经验+题目要求)

dp[i]表示以i位置为结尾所有子序列中,最长的等差子序列的长度(错误)。

因为我们至少得确定两个位置,才能知道序列是否满足等差子序列的要求。

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

 2、状态转移方程

dp[i][j]:  (假设abc对应的坐标分别是kij)

(1)如果a存在且a<b——>dp[i][j]+=dp[k][i]+1

(2)a存在且b<a<c——>0

(3)a不存在——>0

       我们固定两个数用了两层for循环了,如果找第三个数的时候还要在前面用一层for循环的话,那么就是n^3的时间复杂度了,所以我们可以用哈希表来帮助我们存储下标和元素的映射关系。并且我们只需要保存靠后的元素下标即可。

优化思路:

(1)将元素与下标绑定存放在哈希表中。(该题需要统计所有的子序列,所以相同元素下标不同的情况都要统计,因此我们要将元素绑定一个下标数组)

(2)i位置填完后,将i位置的值放进哈希表中

3、初始化

都初始化为0

4、填表顺序

       先固定倒数第二个数,再枚举最后一个数(可以等i位置填完后再将i的位置丢进哈希表,这样可以保证哈希表内的元素的下标必然是小的,就可以不需要判断b<a<c的情况)

5、返回值

dp表的总和——用一个sum去统计

 6、细节处理

可能会溢出,所以我们要下标要存储long

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1796381.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

智能电销机器人的作用和原理是什么?

要问世界上更火爆的创新技术&#xff0c;人工智能必然要算其一&#xff0c;人工智能正不断的改变着我们的生活&#xff0c;比如智能手机、智能家居、智能门锁等产品已经不断的渗透在了我们的生活之中&#xff0c;而近几年兴起的人工智能语音识别机器人&#xff0c;也迅速俘获了…

Centos7安装ElasticSearch

Centos7安装ElasticSearch 准备工作 下载elasticsearch https://www.elastic.co/cn/elasticsearch 将下载好的包上传到/usr/local/elasticsearch/ 路径下 安装 安装elasticsearch解压缩即可&#xff01; tar -zxvf elasticsearch-8.12.2-linux-x86_64.tar.gz进入/usr/loca…

使用Django Channels和WebSocket构建聊天应用

一、引言 WebSocket提供了一种在客户端和服务器之间进行实时双向通信的方式。结合Django Channels&#xff0c;我们可以轻松地在Django项目中实现WebSocket功能。本文将通过一个简单的聊天应用示例&#xff0c;展示如何使用Django Channels和WebSocket。 二、环境搭建 项目的…

Spring Boot 整合开源 Tess4J库 实现OCR图片文字识别

&#x1f604; 19年之后由于某些原因断更了三年&#xff0c;23年重新扬帆起航&#xff0c;推出更多优质博文&#xff0c;希望大家多多支持&#xff5e; &#x1f337; 古之立大事者&#xff0c;不惟有超世之才&#xff0c;亦必有坚忍不拔之志 &#x1f390; 个人CSND主页——Mi…

Linux基础 (十二):Linux 线程的创建与同步

本篇博客详细介绍与线程有关的内容&#xff0c;这部分也是笔试面试的重点&#xff0c;需要我们对线程有深刻的理解&#xff0c;尤其是线程的并发运行以及线程同步的控制&#xff01;接下来&#xff0c;让我们走进线程的世界&#xff0c;去理解线程&#xff0c;使用线程&#xf…

修改west扩展命令的路径

west命令是zephyr工程中非常重要的工具。使用west命令&#xff0c;可以高效的创建工程&#xff0c;管理代码&#xff0c;此外&#xff0c;通过扩展命令&#xff0c;还可以支持编译&#xff0c;烧录等功能。 从下图中可以看出&#xff0c;extension commands from project mani…

线性代数|机器学习-P8矩阵低秩近似eckart-young

文章目录 1. SVD奇异值分解2. Eckart-Young2.1 范数 3. Q A Q U Σ V T QAQU\Sigma V^T QAQUΣVT4. 主成分分析图像表示 1. SVD奇异值分解 我们知道&#xff0c;对于任意矩阵A来说&#xff0c;我们可以将其通过SVD奇异值分解得到 A U Σ V T AU\Sigma V^T AUΣVT&#xff0…

[ue5]建模场景学习笔记(4)——必修内容可交互的地形,交互沙(1)

1.需求分析&#xff1a; 现在的沙漠场景仅仅只是一张贴图&#xff0c;人物走过不会留下任何痕迹&#xff0c;很不真实&#xff0c;尝试优化一下&#xff0c;做出可交互的沙漠效果。 2.操作实现&#xff1a; 1.思路&#xff1a;这是一个相对复杂的工程&#xff0c;要考虑玩家踩…

深入理解C++三五零法则

三五零法则就是三法则&#xff08;The Rule of Three&#xff09;、五法则&#xff08;The Rule of Five&#xff09;、零法则&#xff08;The Rule of Zero&#xff09;。三五零法则是和C的特殊成员函数有关&#xff0c;特别是那些涉及对象如何被创建、复制、移动和销毁的函数…

ESD防护SP3232E真+3.0V至+5.5V RS-232收发器

特征 采用3.0V至5.5V电源&#xff0c;符合真正的EIA/TIA-232-F标准 满载时最低 120Kbps 数据速率 1μA 低功耗关断&#xff0c;接收器处于活动状态 &#xff08;SP3222E&#xff09; 可与低至 2.7V 电源的 RS-232 互操作 增强的ESD规格&#xff1a; 15kV人体模型 15kV IEC1000…

Java Web学习笔记17——Vue快速入门

什么是Vue&#xff1f; Vue是一套前端框架&#xff0c;免除原生JavaScript中的DOM操作&#xff0c;简化书写。 基于MVVM&#xff08;Model-View-ViewModel&#xff09;思想&#xff0c;实现数据的双向绑定&#xff0c;将编程的关注点放在数据上。 官网&#xff1a;https://v…

概率分析和随机算法

目录 雇佣问题 概率分析 随机算法 生日悖论 随机算法 概率分析 球与箱子 总结 雇佣问题 有n个候选人面试&#xff0c;如果面试者比目前雇佣者的分数高&#xff0c;评价更好&#xff0c;那么就辞掉当前雇佣者&#xff0c;而去聘用面试者&#xff0c;否则继续面试新的候…

区块链简要介绍及运用的技术

一、区块链的由来 区块链概念最早是从比特币衍生出来的。 比特币&#xff08;Bitcoin&#xff09;诞生于2008年&#xff0c;是由一个名叫中本聪&#xff08;Satoshi Nakamoto&#xff09;的人首次提出&#xff0c;这个人非常神秘&#xff0c;至今没有他的任何准确信息。在提出…

三、【源码】Mapper XML的解析和注册使用

源码地址&#xff1a;https://github.com/mybatis/mybatis-3/ 仓库地址&#xff1a;https://gitcode.net/qq_42665745/mybatis/-/tree/03-parse-mapperXML Mapper XML的解析和注册使用 流程&#xff1a; 1.Resources加载MyBatis配置文件生成Reader字符流 2.SqlSessionFact…

Activity->Activity中动态添加Fragment->add和replace方式添加的区别

XML文件 Activity布局文件R.layout.activity_main <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:id"id/root_ll"android:orientation"v…

一个简单好用的 C# Animation Easing 缓动动画类库

文章目录 1. 类库说明2.使用步骤2.1 创建一个Windows Form 项目2.2 安装类库2.3 编码效果3.代码下载1. 类库说明 App.Animations 类库是一个很精炼、好用的 csharp easing 动画库 基于 net-standard 2.0提供 Fluent API,写代码非常舒服。支持多个参数同时参与动画。自带了十几…

Flutter基础 -- Flutter常用组件

目录 1. 文本组件 Text 1.1 基础用法 1.2 Text 定义 1.3 Text 示例 1.4 Text.rich、RichText 、TextSpan 1.5 RichText 示例 2. 导入资源 2.1 加入资源 2.2 加入图片 3. 图片组件 image 3.1 colorBlendMode 混合参数 3.2 fit 图片大小适配 3.3 ImageProvider 图片…

【Python报错】已解决NameError: name ‘xxx‘ is not defined

解决Python报错&#xff1a;NameError: name ‘xxx’ is not defined 在Python编程中&#xff0c;NameError是一个非常常见的错误类型&#xff0c;它发生在你尝试访问一个未被定义的变量时。本文将介绍这种错误的原因&#xff0c;以及如何通过具体的代码示例来解决这个问题。 …

深度学习笔记: 最详尽LinkedIn Feed 排名系统设计

欢迎收藏Star我的Machine Learning Blog:https://github.com/purepisces/Wenqing-Machine_Learning_Blog。如果收藏star, 有问题可以随时与我交流, 谢谢大家&#xff01; LinkedIn Feed 排名 1. 问题陈述 设计一个个性化的LinkedIn Feed&#xff0c;以最大化用户的长期参与度…

【MMU】——ARM 一级页表

文章目录 一级页表项即 entry 的格式如下 从上图可以看出 L1 页表项有四种可能类型 产生中止异常的故障条目。这可能是预取或数据中止、取决于访问类型。这实际上表示虚拟地址未映射 bit[1:0] = 00指向 L2 转换表的条目。这样就能将 1MB 的内存分页 bit[1:0] = 01。1MB 段转换…