【动态规划】C++ 子序列问题(递增子序列、数对链、定差子序列、斐波那契子序列...)

文章目录

  • 1. 前言
  • 2. 例题
    • 最长递增子序列
  • 3. 算法题
    • 3.1_摆动序列
    • 3.2_最长递增子序列的个数
    • 3.3_最长数对链
    • [3.4_ 最长定差子序列](https://leetcode.cn/problems/longest-arithmetic-subsequence-of-given-difference/description/)
    • 3.5_最长的斐波那契子序列的长度
    • 3.6_最长等差数列
    • 3.7_等差数列划分II-子序列

1. 前言

关于 动态规划的理解 与例题,点击👇

【动态规划】C++解决斐波那契模型题目(三步问题、爬楼梯、解码方法…)

并且我们之前做过有关 子数组的dp问题:C++子数组dp问题

子序列与子数组的区别主要在于:子数组是连续的,即下标连续的不中断的;而子序列可以连续可以不连续(子序列的范围更大)。

有了上面的经验,我们来解下面 子序列 问题

2. 例题

通过下面一道例题理解子序列问题,以及如何解子序列dp问题:

最长递增子序列

在这里插入图片描述

思路

  • 题意分析
    1. 题目要求找到 最长的递增子序列的长度

根据题目要求,我们设置状态表示:

在这里插入图片描述
根据上面的思路,我们可以用两个for循环编写呆猫:

代码

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
         // 创建dp数组 + 初始化
        vector<int> dp(n, 1); // dp[i]: 以i为结尾,严格递增子序列的最长长度
        
        int ret = 1; // 结果:
        for(int i = 1; i < n; ++i)
        {
            for(int j = 0; j <= i-1; ++j)
            {
                if(nums[j] < nums[i])
                    dp[i] = max(dp[j]+1, dp[i]);
            }
            ret = max(ret, dp[i]);
        }

        return ret;
    }
};

需要注意的是:本题的最优解法并不是利用动态规划,但通过本道例题可以很好的对子序列问题进行理解:

(顺带贴上更优解法:贪心 + 二分)

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        // 贪心
        vector<int> ret;
        ret.push_back(nums[0]);

        for(int i = 0; i < nums.size(); ++i)
        {
            if(nums[i] > ret.back()) {
                ret.push_back(nums[i]);
            } else { // 二分查找
                int left = 0, right = ret.size() - 1;
                while(left < right)
                {
                    int mid = (left + right) >> 1;
                    if(ret[mid] < nums[i])
                        left = mid + 1;
                    else
                        right = mid;
                }
                ret[left] = nums[i]; // 插入
            }
        }

        return ret.size();
    }
};

3. 算法题

通过《最长递增子序列》一题给的经验,我们来解下面的算法题:

3.1_摆动序列

在这里插入图片描述

思路

  • 题意分析
    1. 在子数组问题中,我们曾做过一道题《最长湍流子数组》,和本题类似,我们根据它的经验,可以创建两个dp表:

在这里插入图片描述
在这里插入图片描述

代码

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        int n = nums.size();

        // dp数组的创建 + 元素初始化
        vector<int> f(n, 1); // 以i为结尾,最后呈现“上升”状态的 子序列的最长长度
        vector<int> g(n, 1); // 以i为结尾,最后呈现“下降”状态的 子序列的最长长度

        int ret = 1; // 最终结果
        for(int i = 1; i < n; ++i)
        {
            for(int j = 0; j <= i - 1; ++j)
            {
                if(nums[j] < nums[i]) 
                    f[i] = max(g[j]+1, f[i]); // 可以将第 i 个元素加入到以第 j 个元素结尾的递增子序列中
                if(nums[j] > nums[i]) 
                    g[i] = max(f[j]+1, g[i]);
            }
            ret = max(max(f[i], g[i]), ret);
        }

        return ret;
    }
};

3.2_最长递增子序列的个数

在这里插入图片描述

思路

  • 题意分析
    1. 之前的例题,求的是最长递增子序列的长度,这里要的是 最长递增子序列的 个数
    2. 即我们不仅需要考虑长度,也需要考虑个数,即两个状态,可以设置两个dp表
    3. 一个小demo: 如何找到数组中最大值的出现次数?
      • 遍历数组会有三种情况:
        • x < maxVal: 无视
        • x == maxVal: count++
        • x > maxVal: 更新maxVal,count = 0

根据这个思路,我们进行分析:

在这里插入图片描述

在这里插入图片描述

代码

class Solution {
public:
    int findNumberOfLIS(vector<int>& nums) {
        int n = nums.size();

        // 创建dp数组 + 初始化元素
        vector<int> len(n, 1), count(n, 1); // 分别为:以i为结尾,1.递增子序列的最长长度 与 2.最长递增子序列的个数

        int retLen = 1, retCount = 1;
        for(int i = 1; i < n; ++i)
        {
            for(int j = 0; j <= i-1; ++j)
            {
                if(nums[j] < nums[i])
                {
                    if(len[j] + 1 == len[i]) count[i] += count[j]; // 第j位恰好与i组成最长递增子序列
                    // else if(len[j] + 1 < len[i]) continue; // 无视此次,可以注释掉
                    else if(len[j] + 1 > len[i]) len[i] = len[j] + 1, count[i] = count[j]; // j的递增序列更长
                }
            }
            // 更新结果
            if(retLen == len[i]) retCount += count[i];
            else if(retLen < len[i]){
                retCount = count[i];
                retLen = len[i];
            }
            // else 无视该情况
        }

        return retCount;
    }
};

3.3_最长数对链

在这里插入图片描述

思路

  • 题意分析
    1. 题目要求找到 最长的数对链,数对链即对于[a, b], [c, d]仅当b < c时,才成立[a, b] -> [c, d]
    2. 根据这个规律,我们在找子序列时,首先要保证不能存在一种情况:
      • 令存在三个数对x, y, z
      • 当我们遍历到y时,即使不一定有y->z,但一定不能有z->y
    3. 根据数对链的性质,我们只需要 对数组根据首位元素进行排序 即可。
    4. 由此可以设置状态表示:

在这里插入图片描述

代码

class Solution {
public:
    int findLongestChain(vector<vector<int>>& pairs) {
        // 预处理:排序数组
        sort(pairs.begin(), pairs.end());
        
        int n = pairs.size();
         // 创建dp数组 + 初始化元素
        vector<int> dp(n, 1); // dp[i]: 以i为结尾,数对链的最长长度

        int ret = 1; // 结果
        for(int i = 1; i < n; ++i)
        {
            for(int j = 0; j <= i-1; ++j)
            {
                if(pairs[j][1] < pairs[i][0])
                {
                    dp[i] = max(dp[j] + 1, dp[i]);
                }
            }
            ret = max(ret, dp[i]);
        }

        return ret;
    }
};

3.4_ 最长定差子序列

在这里插入图片描述

思路

  • 题意分析
    1. 题目要求找 最长的定差子序列的长度 ,定差子序列即等差数列,给定了公差difference。
    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, n = arr.size();
        for(int i = 1; i < n; ++i)
        {
            hash[arr[i]] = hash[arr[i] - difference] + 1; // dp[i] = dp[j] + 1,可以保证是最后一个b
            ret = max(ret, hash[arr[i]]);
        }

        return ret;
    }
};

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

在这里插入图片描述

思路

  • 题意分析
    1. 题目要求找 最长的斐波那契子序列的长度
    2. 我们首先试着将状态表示设置为:
      • dp[i]:以i为结尾的所有子序列中,最长斐波那契子序列的长度
      • 当我们尝试写状态表达式时,没有办法正确找到(斐波那契子序列的判断至少需要两个元素,通过差值我们可以判断另一位是什么)
    3. 所以我们更改状态表示:

在这里插入图片描述
在这里插入图片描述

代码

class Solution {
public:
    int lenLongestFibSubseq(vector<int>& arr) {
        int n = arr.size();

        // 创建dp数组 + 元素初始化
        vector<vector<int>> dp(n, vector<int>(n, 2));
        // 哈希表:值映射下标
        unordered_map<int, int> hash;
        for(int i = 0; i < arr.size(); ++i)
            hash[arr[i]] = i; 

        int ret = 2; // 返回值
        for(int j = 2; j < n; ++j)
        {
            for(int i = 1; i < j; ++i)
            {
                // 填表
                int a = arr[j] - arr[i];
                if(a < arr[i] && hash.count(a))
                    dp[i][j] = dp[hash[a]][i] + 1; // 
                ret = max(ret, dp[i][j]);
            }
        }

        return ret == 2 ? 0 : ret;
    }
};

3.6_最长等差数列

在这里插入图片描述

思路

  • 题意分析
    1. 本题与前面的斐波那契子序列有相似之处,下面进行状态表示的设置:

在这里插入图片描述
在这里插入图片描述

代码

class Solution {
public:
    int longestArithSeqLength(vector<int>& nums) {
        int n = nums.size();
        unordered_map<int, int> hash; // <元素 下标>
        hash[nums[0]] = 0;

        vector<vector<int>> dp(n, vector<int>(n, 2));// dp表的创建+初始化
        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; // dp[k][i] + 1
                ret = max(ret, dp[i][j]);
            }
            hash[nums[i]] = i; // 保存最近的元素下标 
        }
        return ret;
    }
};

3.7_等差数列划分II-子序列

在这里插入图片描述

思路

  • 题意分析
    1. 前面的题要求最长等差数列的长度,而本题要求个数
    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<long long>> dp(n, vector<long long>(n, 0));// dp表的创建+初始化
        long long 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; // += dp[k][i] + 1
                sum += dp[i][j];
            }
        }
        return sum;
    }
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

python-flask结合bootstrap实现网页小工具实例-半小时速通版

参考&#xff1a; Python之flask结合Bootstrap框架快速搭建Web应用_支持bootstrap的python软件-CSDN博客 https://blog.csdn.net/lovedingd/article/details/106696832 Bootstrap 警告框 | 菜鸟教程 https://www.runoob.com/bootstrap/bootstrap-alert-plugin.html flask框架…

用C#写一个读取pdf文档内容的库

安装这两个库&#xff0c;第二个库一定要安装否则有些pdf文件读取会出现异常 读取 using iText.Kernel.Pdf; using iText.Kernel.Pdf.Canvas.Parser; using iText.Kernel.Pdf.Canvas.Parser.Listener;namespace TestReadPdf {public static class PdfHelper{public static IE…

算法刷题day46

目录 引言一、树的重心二、毕业旅行问题三、高精度乘法 引言 今天复习了一下高精度的所有模板&#xff0c;包括加法、减法、乘法、除法&#xff0c;因为自己当时在蓝桥杯的时候没有看出来那个题使用高精度&#xff0c;因为对于一个数的大小和一个数的长度&#xff0c;自己有时…

关于卡尔曼滤波进行状态预测的方法

卡尔曼滤波是一种有效的线性动态系统状态估计方法。它通过递归地处理测量数据&#xff0c;结合系统动力学模型和测量模型&#xff0c;来预测和估计系统的状态。卡尔曼滤波特别适用于系统状态在时间上演化&#xff0c;而且测量数据存在噪声的情况。以下是卡尔曼滤波对于状态预测…

编译器的学习

常用的编译器&#xff1a; GCCVisual CClang&#xff08;LLVM&#xff09;&#xff1a; Clang 可以被看作是建立在 LLVM 之上的一个项目, 实际上LLVM是clang的后端&#xff0c;clang作为前端前端生成LLVM IR&#xff0c;https://zhuanlan.zhihu.com/p/656699711MSVC &#xff…

[C++][算法基础]能被整除的数(容斥原理)

给定一个整数 &#x1d45b; 和 &#x1d45a; 个不同的质数 &#x1d45d;1,&#x1d45d;2,…,&#x1d45d;&#x1d45a;。 请你求出 1∼&#x1d45b; 中能被 &#x1d45d;1,&#x1d45d;2,…,&#x1d45d;&#x1d45a; 中的至少一个数整除的整数有多少个。 输入格式…

【Java Spring MVC项目异常解决】HTTP 500

HTTP 500状态码表示“内部服务器错误”&#xff08;Internal Server Error&#xff09;。这是一个通用的错误响应&#xff0c;表明服务器在处理请求时遇到了预料之外的情况&#xff0c;导致无法完成请求。500错误是服务器端错误的一种&#xff0c;与客户端无关。在Web开发中&am…

初识《list》及手搓模拟《list》

目录 前言&#xff1a; 1. list的介绍及使用 list的介绍&#xff1a; list的使用&#xff1a; 1、list的构造​编辑 2、list iterator的使用 3、list capacity 4、list element access 5、list modifiers 2.list的模拟实现 1、关于迭代器&#xff1a; 2、迭代器类的…

代码质量与可维护性的重要性都有哪些?

目录 一、为了提高代码质量&#xff0c;可以采取以下几种方法&#xff1a; 二、如何制定和执行有效的代码编写规范&#xff1f; 三、设计模式和设计原则在提高代码质量中的具体应用案例有哪些&#xff1f; 四、代码审查的最佳实践和技巧是什么&#xff1f; 五、如何有效地…

CV每日论文--2024.4.24

1、Guess The Unseen: Dynamic 3D Scene Reconstruction from Partial 2D Glimpses 中文标题&#xff1a;猜测未见之景&#xff1a;从部分二维片段进行动态三维场景重建 简介&#xff1a;这篇论文提出了一种方法&#xff0c;可以从单目视频输入中重建世界和多个动态人物的3D模…

猫主食罐要怎么挑?注意这些含胶的罐头!

我曾与专业的宠物医生深入交流&#xff0c;得知猫罐头的种类与选择不可一概而论。主食罐头营养搭配精细&#xff0c;旨在全面满足猫咪健康需求&#xff0c;常添加矿物质和维生素&#xff0c;并针对不同猫咪有特定配方。而零食罐头更重口感与美味&#xff0c;钠含量高&#xff0…

如何提取单片机片内程序的值进行拷贝?

对于许多单片机&#xff0c;其固件是由制造商保护的&#xff0c;并且未经授权的访问、拷贝或修改可能侵犯法律。我这里有一套嵌入式入门教程&#xff0c;不仅包含了详细的视频 讲解&#xff0c;项目实战。如果你渴望学习嵌入式&#xff0c;不妨点个关注&#xff0c;给个评论222…

跨部门协作中的沟通困境与平台建设策略——以软硬件研发为例

一、背景 在科技行业&#xff0c;跨部门合作的重要性不言而喻&#xff0c;然而实际工作中&#xff0c;经常会遭遇沟通不畅的现象。以软件与硬件研发部门为例&#xff0c;两者在产品研发过程中经常需要紧密协作&#xff0c;但却时常出现信息传递障碍。当你试图阐述观点时&#…

LangSmith帮助测试大模型系统

LangSmith是评估大模型能力好坏的评估工具,能够量化评估基于大模型的系统的效果。LangSmith通过记录langchain构建的大模型应用的中间过程,从而能够更好的调整提示词等中间过程做优化。想要使用LangSmith首先进入他的设置页面,https://smith.langchain.com/settings注册一个…

多商家AI智能名片商城系统(开源版)——构建高效数字化商业新生态

一、项目概述 1、项目背景 1&#xff09;起源 随着数字化时代的快速发展&#xff0c;传统名片和商城系统已经难以满足企业日益增长的需求。商家需要更高效、更智能的方式来展示自己的产品和服务&#xff0c;与消费者进行互动和交易。同时&#xff0c;开源技术的普及也为开发…

安卓玩机工具推荐----MTK芯片 简单制作线刷包 备份分区 备份基带 去除锁类 推荐工具操作解析

工具说明 在前面几期mtk芯片类玩机工具中解析过如何无官方固件从手机抽包 制作线刷包的步骤&#xff0c;类似的工具与操作有很多种。演示的只是本人片面的理解与一些步骤解析。mtk芯片机型抽包关键点在于..mt*****txt的分区地址段引导和 perloader临时分区引导。前面几期都是需…

在控制台实现贪吃蛇

在控制台实现贪吃蛇 前备知识Win32APICOORD这个结构体的声明如下&#xff1a;GetStdHandle 函数GetConsoleCursorInfo 函数SetConsoleCursorInfo 函数 SetConsoleCursorPosition 函数getAsyncKeyState 函数 控制台窗口的大小以及字符打印介绍控制台中的坐标宽字符及本地化介绍s…

多线程情况下IBMMQ报文丢失原因分析

背景 最近工作中&#xff0c;使用IBMMQ&#xff0c;重启服务时有偶发性的报文丢失情况&#xff0c;应用从队列中获取到了消息&#xff0c;但是线程停止没有处理。 分析 消息处理线程流程&#xff1a; 判断线程状态是否可用&#xff0c;如果不可用直接返回。使用MQQueue.get…

Seurat -- Introduction to scRNA-seq integration 跟随学习记录

文章目录 数据是如何转换的原始ifnb数据对象Splits object后的数据对象数据对象构建完成后的标准流程Normalization后的数据对象scale 后的数据对象 不同的样本进行整合JoinLayers干了什么 数据是如何转换的 seurat object 中assays R N A l a y e r s RNAlayers RNAlayersco…

卡尔曼滤波器(一):卡尔曼滤波器简介

观看MATLAB技术讲座笔记&#xff0c;该技术讲座视频来自bilibili账号&#xff1a;MATLAB中国。 一、什么是卡尔曼滤波器 卡尔曼滤波器是一种优化估计算法&#xff0c;是一种设计最优状态观测器的方法&#xff0c;其功能为&#xff1a; 估算只能被间接测量的变量&#xff1b;通…