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

news2024/10/6 2:28:41

文章目录

  • 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;
    }
};

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

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

相关文章

剪画APP动漫AI工具|短剧轻松变动漫,开辟出海新方向

近期&#xff0c;剪画全新升级“AI动漫”功能&#xff0c;让创作者们打开全新的视频创作方式。 这个功能把我们的短剧作品快速转化为动漫风格&#xff0c;非常精准和细腻 在剪画将AI动漫功能升级后&#xff0c;越来越多的使用者将短剧变身成动漫效果发布到平台后&#xff0c;…

Git 保姆级教程(一):Git 基础

一、获取 Git 仓库 通常有两种获取 Git 项目仓库的方式&#xff1a; 1. 将尚未进行版本控制的本地目录转换为 Git 仓库&#xff1b; 2. 从其它服务器克隆 一个已存在的 Git 仓库。 两种方式都会在你的本地机器上得到一个工作就绪的 Git 仓库。 1.1 git init&#xff08;本地…

Fisher判别示例:鸢尾花(iris)数据(R)

先读取iris数据&#xff0c;再用程序包MASS&#xff08;记得要在使用MASS前下载好该程序包&#xff09;中的线性函数lda()作判别分析&#xff1a; data(iris) #读入数据 iris #展示数据 attach(iris) #用变量名绑定对应数据 library(MASS) #加载MASS程序包 ldlda(Species~…

c++的策略模式,就是多态

一、定义&#xff1a; 策略模式定义了一系列的算法&#xff0c;并将每一个算法封装起来&#xff0c;而且使它们还可以相互替换。 策略模式让算法独立于使用它的客户而独立变化。 二&#xff0c;核心 抽象策略&#xff08;抽象基类&#xff09;&#xff08;Strategy&#xff09…

Fork for Mac v2.42 激活版 Git客户端

Fork for Mac是一款运行在Mac平台上的Git客户端&#xff0c;Fork Mac版具备基本的取、推、提交、修改、创建和删除分支和标签、创建和删除远程备份等功能&#xff0c;还有实用的差异查看器&#xff0c;你可以通过清晰的视图快速发现源代码中的更改。 Fork for Mac v2.42 激活版…

【C++】优先队列

优先队结构的不同物理结构与常用操作算法 优先队列是一种特殊的队列,队列中的元素具有优先级,每次弹出操作会弹出优先级最高的元素。 优先队列常用的物理结构有: 1. 数组:简单但不高效,插入和删除操作需要移动大量元素,时间复杂度高。 2. 二叉堆:是一种完全二叉树,通常用数…

在Jupyter notebook中添加虚拟环境

通常我们打开Jupyter notebook&#xff0c;创建一个新文件&#xff0c;只有一个Python3&#xff0c;但是我们也会想使用自己创建的虚拟环境&#xff0c;很简单仅需几部即可将自己的conda环境添加到jupyter notebook中。 1. 创建并激活conda环境&#xff08;已有可跳过&#xf…

【QT进阶】Qt http编程之实现websocket server服务器端

往期回顾 【QT进阶】Qt http编程之json解析的简单介绍-CSDN博客 【QT进阶】Qt http编程之nlohmann json库使用的简单介绍-CSDN博客 【QT进阶】Qt http编程之websocket的简单介绍-CSDN博客 【QT进阶】Qt http编程之实现websocket server服务器端 一、最终效果 通过ip地址和端口…

Redis入门到通关之Redis数据结构-Hash篇

文章目录 ☃️ 概述☃️底层实现☃️源码☃️其他 欢迎来到 请回答1024 的博客 &#x1f353;&#x1f353;&#x1f353;欢迎来到 请回答1024的博客 关于博主&#xff1a; 我是 请回答1024&#xff0c;一个追求数学与计算的边界、时间与空间的平衡&#xff0c;0与1的延伸的后…

使用 vllm 本地部署 cohere 的 command-r

使用 vllm 本地部署 cohere 的 command-r 0. 引言1. 安装 vllm2. 本地部署 cohere 的 command-r3. 使用 cohere 的 command-r 0. 引言 此文章主要介绍使用 使用 vllm 本地部署 cohere 的 command-r。 1. 安装 vllm 创建虚拟环境&#xff0c; conda create -n myvllm python…

微软开源了Phi-3-mini适用于移动硬件设备

&#x1f989; AI新闻 &#x1f680; 微软开源了Phi-3-mini适用于移动硬件设备 摘要&#xff1a;微软最新开源的小参数大语言模型Phi-3-mini&#xff0c;包括其架构特点、训练数据、性能测试以及未来发布计划。该模型拥有38亿参数&#xff0c;占用内存少&#xff0c;且在语言…

JVM--Java对象到底存在哪?

Java对象存放在堆中&#xff0c;但堆又分为新生代和老年代&#xff0c;新生代又细分为 Eden、From Survivor、To Survivor。那我们创建的对象到底在哪里&#xff1f; 堆分为新生代和老年代&#xff0c;新生代用于存放使用后就要被回收的对象&#xff08;朝生夕死&#xff09;&a…

单片机学习过程

继电器光耦隔离电压转换步进电机直流电机 arduino是目前最好用的一种&#xff0c;他提供了完整的设备库文件&#xff0c;任何外部设备只要查找相应的库&#xff0c;就可以很方便的使用 &#xff0c; 但是如果不去学习51 或stm32 或 嵌入式玩玩还可以&#xff0c;如果碰到没有实…

Navicat和MySQL的安装、破解以及MySQL的使用(详细)

1、下载 Navicat Navicat 官网&#xff1a;www.navicat.com.cn/ 在产品中可以看到很多的产品&#xff0c;点击免费试用 Navicat Premium 即可&#xff0c;是一套多连数据库开发工具&#xff0c;其他的只能连接单一类型数据库 点击试用 选择系统直接下载 二、安装 Navicat 安…

【C++】---STL之vector的模拟实现

【C】---STL之vector的模拟实现 一、vector在源码中的结构&#xff1a;二、vector类的实现&#xff1a;1、vector的构造2、析构3、拷贝构造4、赋值运算符重载5、迭代器6、operator[ ]7、size()8、capacity()9、reserve()10、resize()11、empty()12、push_back()13、pop_back()1…

Pytorch常用的函数(八)常见优化器SGD,Adagrad,RMSprop,Adam,AdamW总结

Pytorch常用的函数(八)常见优化器SGD,Adagrad,RMSprop,Adam,AdamW总结 在深度学习中&#xff0c;优化器的目标是通过调整模型的参数&#xff0c;最小化&#xff08;或最大化&#xff09;一个损失函数。 优化器使用梯度下降等迭代方法来更新模型的参数&#xff0c;以使损失函数…

【JavaScriptthreejs】对于二维平面内的路径进行扩张或缩放

目标 对指定路径 [{x,y,z},{x,y,z},{x,y,z},{x,y,z}.........]沿着边缘向内或向外扩张&#xff0c;达到放大或缩小一定范围的效果&#xff0c;这里我们获取每个点&#xff08;这里是Vector3(x,y,z)&#xff09;,获取前后两个点和当前点的坐标&#xff0c;计算前后两点的向量&a…

AJAX——案例

1.商品分类 需求&#xff1a;尽可能同时展示所有商品分类到页面上 步骤&#xff1a; 获取所有的一级分类数据遍历id&#xff0c;创建获取二级分类请求合并所有二级分类Promise对象等待同时成功后&#xff0c;渲染页面 index.html代码 <!DOCTYPE html> <html lang&qu…

【数据库】MongoDB

文章目录 [toc]数据库操作查询数据库切换数据库查询当前数据库删除数据库查询数据库版本 数据集合操作创建数据集合查询数据集合删除数据集合 数据插入插入id重复的数据 数据更新数据更新一条丢失其他字段保留其他字段 数据批量更新 数据删除数据删除一条数据批量删除 数据查询…

S-Edge网关:柔性部署,让物联网接入更统一

S-Edge网关是什么&#xff1f; 网关是在实际物理世界与虚拟网络世界相连接的交叉点&#xff0c;为了让这个交叉点尽可能的复用&#xff0c;无需每种设备都配套一种连接方式&#xff0c;边缘网关主要就是用于传感器等物理设备与网络实现数据交互的通用设备&#xff0c;也称为物…