DP:子序列问题

news2025/1/11 0:55:02

文章目录

  • 什么是子序列
    • 子序列的特点
    • 举例说明
    • 常见问题
  • 关于子序列问题的几个例题
    • 1.最长递增子序列
    • 2.摆动序列
    • 3.最长递增子序列的个数
    • 4.最长数对链
    • 5.最长定差子序列
  • 总结

在这里插入图片描述

什么是子序列

在计算机科学和数学中,子序列(Subsequence)是指从一个序列中删除一些元素(可以是零个或多个),但不改变其余元素相对顺序后形成的新序列。

子序列的特点

元素的相对顺序保持不变。
可以删除零个或多个元素。
一个序列的子序列可以为空序列,即不包含任何元素。

举例说明

设有序列 S = [A, B, C, D, E],则其子序列可以有:
删除零个元素:[A, B, C, D, E](即自身)
删除一个元素:[A, B, C, D]、[A, B, C, E]、[A, B, D, E]、[A, C, D, E]、[B, C, D, E]
删除两个元素:[A, B, C]、[A, B, D]、[A, B, E]、[A, C, D]、[A, C, E]、[A, D, E]、[B, C, D]、[B, C, E]、[B, D, E]、[C, D, E]
删除三个元素:[A, B]、[A, C]、[A, D]、[A, E]、[B, C]、[B, D]、[B, E]、[C, D]、[C, E]、[D, E]
删除四个元素:[A]、[B]、[C]、[D]、[E]
删除所有元素:[](空序列)

常见问题

子序列问题在算法设计和编程竞赛中非常常见。以下是几种经典问题:
最长公共子序列(LCS):给定两个序列,找出它们的最长公共子序列。动态规划是解决这个问题的常用方法。
最长递增子序列(LIS):给定一个序列,找出其中最长的递增子序列。可以使用动态规划或贪心算法结合二分查找解决。
子序列和问题:给定一个序列,找出所有和为特定值的子序列。可以使用回溯法或动态规划解决。

根据我上面的介绍,可以总结,大多数子序列问题其实都可以用DP的算法来解决。

关于子序列问题的几个例题

1.最长递增子序列

题目链接
题目:

在这里插入图片描述

样例 输出和输入:

在这里插入图片描述

首先根据上述子序列的描述,这道题就很容易理解了,就是 让我们求给定数组的最长的递增子序列。
算法原理:
状态表示:dp[i]表示以i位置为结尾的所有子序列中最长的那个子序列的长度。
状态转移方程:
在这里插入图片描述
首先我们要求状态转移方程就要看i位置的状态,我们要确定i位置的状态,是不是应该将0到i-1位置遍历一遍,然后将当中的最长子序列求出来然后再加上当前位置的长度1就可以了,这是当子序列长度大于1的时候,还有一种情况是长度等于1的时候,长度等于1的时候,可以默认看做一个子序列,所以dp[i]就等于1,当长度大于1的时候,这种情况,我们先用一个变量j将0到i-1位置的最长子序列遍历出来,然后再+1,所以状态转移方程:dp[i] = max(dp[j]+1,dp[i])
初始化:因为单独一个元素就可以看做一个递增的子序列,所以DP表中的值可以全部初始化为1.
代码展示:

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

运行结果:
在这里插入图片描述

2.摆动序列

题目链接
题目:

在这里插入图片描述

样例 输出和输入:

在这里插入图片描述

这道题让我们求摆动序列的最长的子序列的长度,首先我们要搞清楚,什么是摆动序列:
在这里插入图片描述

上面就是一个摆动序列。
算法原理:
这道题首先要求摆动序列,我们上个专题已经做过类似的题了,就像湍流数组一样,这道题很显然,我们需要两个状态,一个状态是向下的状态,一个状态是向上的状态,这里定义f[i]是向上的状态,g[i]是向下的状态。
状态表示:f[i]是以i位置为结尾的子序列中长度最长且最后一个状态是向上的最长子序列的长度,g[i]表示以i位置为结尾最后的子序列中最后一个状态向下的最长子序列的长度。
状态转移方程:首先对f[i]分析:在这里插入图片描述
所以这里f[i]的状态转移方程:f[i] = max(g[j] + 1, f[i]),同理也可以求出g[i]的状态转移方程:g[i] = max(f[j] + 1, g[i])
代码展示:

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

运行结果:
在这里插入图片描述

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

题目链接
题目:

在这里插入图片描述

样例 输出和输入:

在这里插入图片描述

这道题相对于第一道题换了一个问法。这道题是求最长子序列的个数
算法原理:
状态表示:首先我们先定义一个状态,看这个状态能推下去吗,dp[i]表示以i位置为结尾的所有子序列中,最长子序列的个数。
状态转移方程:首先这里就出问题了 ,这里我们根本不知道最长的子序列是什么,因为根本没有记录的,所以这里根本就推不出来,所以还得加一个len[i],len[i]表示以i位置为结尾的所有子序列中最长子序列的长度,将dp[i]改为count[i],count[i]表示以i位置为结尾的所有子序列中最长的子序列的个数。接下来来推状态转移方程,在这里插入图片描述
有三种情况,当我们遍历的len[j]+1==len[i],意思就是0到i-1位置的子序列中加上当前的长度和之前的最长的子序列是相同的,这里我们应该把以j位置为结尾的最长子序列的个数全部加到count[i]]中。这里画图表示
在这里插入图片描述

根据这些情况可以将表填完,但是,我们还需要 一个retlen和一个retcount更新每次的最长子序列的长度和最长子序列的个数。
这里也分为三种情况,和上面的情况相同,只需要每次遍历完一个位置,更新结果即可。

代码展示:

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 = i - 1;j >= 0;j--)
            {
                //当出现递增的时候
                if (nums[j] < nums[i])
                {
                    //判断如果加上递增的那一个和当前最长的长度还是一样的则需要更新count
                    if (len[j] + 1 == len[i])count[i] += count[j];
                    //如果加上当前的一个元素比比之前的最长的子序列要长,则重新规划长度
                    else if (len[j] + 1 > len[i])count[i] = count[j],len[i] = len[j];
                }
            }
            //统计每次的结果,如果len和结果的len相同,则直接用count累加
            if (retlen == len[i])
                retcount += count[i];
            //如果len比结果的len要大,则直接重置结果len和结果的count
            else if (retlen < len[i])
                retcount = count[i], retlen = len[i];
        }
        return retcount;
    }
};

运行结果:
在这里插入图片描述

4.最长数对链

题目链接
题目:

在这里插入图片描述

样例 输出和输入:

在这里插入图片描述

这道题其实和求最长子序列的长度是相同的题,但是换了一个形式而已,根据题目条件我们可以得知什么是数对链:
在这里插入图片描述
数对连就要满足上述条件
算法原理:
预处理:首先我们得将数组排序,排序的规则,只需要比较每个数对的第一个元素的大小即可,因为每个数对都是单增的,如果我们排序之后保证了a>c,那么d>c是绝对大于前一个数对的,所以这里只需要根据前一个数排序即可。
状态表示:这里dp[i]表示以i位置为结尾的所有数对链中最长的那个数对链的长度。
状态转移方程:分两种情况:
在这里插入图片描述

代码展示:

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 = i - 1;j >= 0;j--)
            {
                if (pairs[j][1] < pairs[i][0])
                {
                    dp[i] = max(dp[j] + 1, dp[i]);
                }
                ret = max(dp[i], ret);
            }
        }
        return ret;
    }
};

运行结果:
在这里插入图片描述

5.最长定差子序列

题目链接
题目:

在这里插入图片描述

样例 输出和输入:

在这里插入图片描述

这道题给定一个difference,让我们求出数组中的差为difference的最长的子序列的长度
算法原理:
状态表示:dp[i]表示以i位置为结尾的所有子序列中的最长的等差子序列,且差值是difference。
状态转移方程:首先我们可以分析一下,我们可以选择从0位置开始遍历寻找和i位置之差是difference的数,这里的dp表其实我们可以借助hash表来充当,因为每次我们都得去前面找和i位置差值是difference的数,所以这里hash表既可以充当dp表,也可以将前一个位置和当前位置的差值是difference的数存起来。
这里的状态转移方程:hash[arr[i]] = hash[arr[i] - difference] + 1这里如果没有在hash表中找到前一个位置差值是difference值的数,则hash[arr[i] - difference]就是0,所以也免去了这种情况,由于我们找的是离i位置最近的前一个位置,这里也可以用hash表解决,因为,我们是从左到右遍历的,这就使得后一个位置每次都是覆盖了前一个位置的值,每次都是最新的状态值。

代码展示:

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++)
        {
            //需要的最后一个b的值,这个hash能保证,因为从左到右遍历,前面的值已经被覆盖了
            hash[arr[i]] = hash[arr[i] - difference] + 1;
            ret = max(ret, hash[arr[i]]);
        }
        return ret;
        
    }
};

运行结果:

在这里插入图片描述

总结

通过本文对子序列问题的探讨,我们深入理解了动态规划在解决此类问题中的重要性。无论是经典的最长公共子序列(LCS)问题,还是最长递增子序列(LIS)问题,动态规划都展示了其强大的解题能力。通过将问题分解为更小的子问题,并记录这些子问题的解,我们能够高效地找到最优解,避免重复计算。

此外,我们还见识了动态规划解决子序列问题的多种变体及其实际应用。这不仅拓宽了我们对算法设计的视野,也提升了我们在面对复杂问题时的解决能力。子序列问题不仅在理论上具有重要意义,也在现实世界中的许多领域,如生物信息学、文本处理和数据分析中有着广泛的应用。

希望通过本文的讲解,读者能对动态规划在子序列问题中的应用有更深的理解,并能将这些技术应用于实际编程中,解决更多实际问题。动态规划的学习不仅仅局限于特定问题,更是培养一种思维方式,一种解决复杂问题的系统方法。愿大家在未来的算法学习和应用中继续精进,取得更大的进步。

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

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

相关文章

继承QAbstractListModel,结合QListView

这里想要写一个QAbstractListModel的子类&#xff0c;学习一下如何实例化QAbstractListModel。 QAbstractListModel子类化-CSDN博客 QVariant与自定义类型互转之奇巧淫技_qt 类型转 qvariant-CSDN博客 #pragma once#include <QStyledItemDelegate> #include <qmeta…

012-GeoGebra基础篇-构造圆的切线

前边文章对于基础内容已经悉数覆盖了&#xff0c;这一篇我就不放具体的细节&#xff0c;若有需要可以复刻一下 目录 一、成品展示二、算式内容三、正确性检查五、文章最后 一、成品展示 二、算式内容 A(0,0) B(3,0) c: Circle(A,B) C(5,4) sSegment(A,C) DMidpoint(s) d: Circ…

javaEE——Servlet

1.web开发概述 所谓web开发,指的是从网页中向后端程序发送请求,与后端程序进行交互 2.java后端开发环境搭建 web后端(javaEE)程序需要运行在服务器中的&#xff0c;这样前端才可以访问得到 3.服务器是什么&#xff1f; ①服务器就是一款软件&#xff0c;可以向其发送请求&#…

分解+降维+预测!多重创新!直接写核心!EMD-KPCA-Transformer多变量时间序列光伏功率预测

分解降维预测&#xff01;多重创新&#xff01;直接写核心&#xff01;EMD-KPCA-Transformer多变量时间序列光伏功率预测 目录 分解降维预测&#xff01;多重创新&#xff01;直接写核心&#xff01;EMD-KPCA-Transformer多变量时间序列光伏功率预测效果一览基本介绍程序设计参…

嵌入式学习——硬件(Linux系统在2440上的启动)——day57

1. Linux2.6系统在s3c2440上的启动过程分三个阶段 1.1 启动u-boot 1.2 启动Linux内核 1.3 挂载根文件系统 2. bootloader 2.1 定义 bootloader的本质是一个裸机程序&#xff0c;bootlood专门是为了能够正确地启动linux操作系 统&#xff0c;在系统初上电时需要对系统做一些…

中霖教育怎么样?咨询工程师备考技巧

中霖教育怎么样?咨询工程师备考技巧 在备考咨询工程师的过程中&#xff0c;掌握正确的方式方法能够少走很多弯路&#xff0c;所以想取得好成绩采用恰当的备考技巧是非常重要的。 1、了解题型及考试结构 在准备阶段&#xff0c;理解各类型题目的特征和作答要求&#xff0c;确…

github仓库的基本使用-创建、上传文件、删除

1.第一步 先点击左侧菜单栏的远程仓库 2.点击NEW 3.创建仓库 然后点击右下角的 CREATE 4.点击code 点击SSH,然后我出现了You don’t have any public SSH keys in your GitHub account. You can add a new public key, or try cloning this repository via HTTPS. 1&#xff…

【JavaEE精炼宝库】多线程进阶(2)synchronized原理、JUC类——深度理解多线程编程

一、synchronized 原理 1.1 基本特点&#xff1a; 结合上面的锁策略&#xff0c;我们就可以总结出&#xff0c;synchronized 具有以下特性(只考虑 JDK 1.8)&#xff1a; 开始时是乐观锁&#xff0c;如果锁冲突频繁&#xff0c;就转换为悲观锁。 开始是轻量级锁实现&#xff…

成人职场商务英语学习柯桥外语学校|邮件中的“备注”用英语怎么说?

在英语中&#xff0c;"备注"通常可以翻译为"Notes" 或 "Remarks"。 这两个词在邮件中都很常用。例如: 1. Notes Notes: 是最通用和最常见的表达&#xff0c;可以用在各种情况下&#xff0c;例如&#xff1a; 提供有关电子邮件内容的附加信息 列…

Mysql并发控制和日志

文章目录 一、并发控制锁机制事务&#xff08;transactions&#xff09;事务隔离级别 二、日志事务日志错误日志通用日志慢查询日志二进制日志 备份在线查看二进制离线查看二进制日志 一、并发控制 锁机制 锁类型&#xff1a; 读锁&#xff1a;共享锁&#xff0c;也称为 S 锁…

ANSYS新能源汽车动力电池仿真应用案例

燃料电池是一种非燃烧过程的电化学能转换装置&#xff0c;将氢气&#xff08;等燃料&#xff09;和氧气的化学能连续不断地转换为电能&#xff0c;是发电设备而非储能设备。 根据电解质的不同&#xff0c;分为碱性燃料电池AFC、磷酸燃料电池PAFC、熔融碳酸盐燃料电池MCFC、固体…

电信NR零流量小区处理

【摘要】随着目前网络建设逐步完善&#xff0c;5G用户的不断发展&#xff0c;针对零流量小区的分析及处理存在着必要性&#xff0c;零流量小区的出现既是用户分布及行为的直观体现&#xff0c;也是发展用户的一个指引&#xff0c;同时也能发现设备的一些故障。一个站点的能够带…

【Python】MacBook M系列芯片Anaconda下载Pytorch,并开发一个简单的数字识别代码(附带踩坑记录)

文章目录 配置镜像源下载Pytorch验证使用Pytorch进行数字识别 配置镜像源 Anaconda下载完毕之后&#xff0c;有两种方式下载pytorch&#xff0c;一种是用页面可视化的方式去下载&#xff0c;另一种方式就是直接用命令行工具去下载。 但是由于默认的Anaconda走的是外网&#x…

瀚高数据库2024最新版_6.0.4_Windows版安装使用---国产瀚高数据库工作笔记007

首先去下载安装包: 下载的是企业版,可以试用一年 首先安装的时候,直接,下一步下一步就可以了 注意要用administrator去安装. 下载以后一步步去安装就可以了 ,桌面上会出现 但是连接不上,并且, 如果从管理工具中,找到服务 cmd services.msc 打开以后,找到瀚高服务,但是…

VuePress日常使用

本篇来讲解下更多关于 VuePress 的基本用法 ‍ 配置首页 现在的页面太简单了&#xff0c;我们可以对项目首页进行配置&#xff0c;修改 docs/README.md &#xff08;这些配置是什么后面会说&#xff09;&#xff1a; --- home: true heroImage: https://s3.bmp.ovh/imgs/20…

zdppy_api+vue3+antd前后端分离开发,使用描述列表组件渲染用户详情信息

后端代码 import api import upload import time import amcomment import env import mcrud import amuserdetailsave_dir "uploads" env.load(".env") db mcrud.new_env()app api.Api(routes[*amcomment.get_routes(db),*amuserdetail.get_routes(db…

利用深度学习模型进行语音障碍自动评估

语音的产生涉及器官的复杂协调&#xff0c;因此&#xff0c;语音包含了有关身体各个方面的信息&#xff0c;从认知状态和心理状态到呼吸条件。近十年来&#xff0c;研究者致力于发现和利用语音生物标志物——即与特定疾病相关的语音特征&#xff0c;用于诊断。随着人工智能&…

Python - 递归函数(Recursive Function)的速度优化 (Python实现)

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/140137432 免责声明&#xff1a;本文来源于个人知识与开源资料&#xff0c;仅用于学术交流&#xff0c;不包含任何商业技术&#xff0c;欢迎相互学…

英灵神殿mac能玩吗 英灵神殿对电脑配置要求《英灵神殿》新手攻略查询 PD虚拟机能玩英灵神殿吗

近年来&#xff0c;随着《英灵神殿》&#xff08;Valheim&#xff09;游戏的火热&#xff0c;越来越多的玩家被其独特的北欧神话题材和丰富的生存挑战所吸引。然而&#xff0c;对于Mac用户来说&#xff0c;如何在Mac平台上运行这款游戏可能是一个问题。此外&#xff0c;作为一名…

闲聊 .NET Standard

前言 有时候&#xff0c;我们从 Nuget 下载第三方包时&#xff0c;会看到这些包的依赖除了要求 .NET FrameWork、.NET Core 等的版本之外&#xff0c;还会要求 .NET Standard 的版本&#xff0c;比如这样&#xff1a; 这个神秘的 .NET Standard 是什么呢&#xff1f; .NET St…