动态规划--子序列问题(一)

一.什么是子序列问题

我们之前已经学习过子数组问题,子数组问题最大的特点就是求一段连续区间的xxxx,子数组问题的经典的状态表示就是以i位置为结束,xxxx,推导状态转移方程的一个经验是根据数组的结构来区分不同的结构

子序列问题本质上是对子数组问题的一个拓展,或者说子序列问题包含了子数组问题

在这里插入图片描述
子序列问题相较于子数组问题最大的不同在于序列可以是不连续的,也就是序列既可以连续又可以不连续,所以说子序列问题包含了子数组问题

但是子序列问题的状态表示和状态转移方程的推导方式和子数组问题十分相似!可以总结为以下步骤:

  1. 根据经验,确定状态表示dp[i],这一步和子数组问题中的状态表示很像,往往都是根据题目的意思+经验确定状态表示
  2. 状态转移方程:子序列/子数组问题的状态转移方程的推导方式比较固定,既按照构成子序列/子数组的形式确定,一般就分为两类
  • 单独一个 nums[i]
  • 前面一堆 + nums[i]
  1. 初始化:对于子序列问题来说,一般单独的一个数字也可以表示一种状态(1),1就表示最次状态,所以往往将dp表初始化为1
  2. 填表顺序:不固定,但一般都是从左往右的线性表
  3. 返回值:具体问题具体分析

下面是一些经典问题:

二.子序列题目讲解

1.最⻓递增⼦序列

链接: 最⻓递增⼦序列

分析:

  1. 状态表示:dp[i]表示以i为结束位置,最长的递增子序列的长度
  2. 状态转移方程:观察构成dp[i]的形式有两种,nums[i]单独一个,此时dp[i]==1,第二种形式是和之前的任意一段子序列重新构成一个新的递增子序列,设前面子序列的结束位置为j,则dp[i] =Max(dp[j] + 1)
  3. 初始化:由于一个nums[i]也可以组成一个子序列,所以最次的状态就是1,进而可以将dp表初始化为1(这样也就可以不讨论状态转移方程中nums[i]单独一个的情况)
  4. 填表顺序:从左至右
  5. 返回值:返回dp[i]的最大值

在这里插入图片描述

代码:

class Solution {
    public int lengthOfLIS(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n];

        for(int i = 0 ; i < n; i++) dp[i] = 1;// 初始化dp表

        int ret = dp[0];// 记录最值

        // 填表
        for(int i = 1; i < n; i++) {
            for(int j = 0; j < i; j++) {
                if(nums[i] > nums[j])// 注意不是任何一个子序列都可以和nums[i]构成严格递增的子序列的  必须符合条件
                    dp[i] = Math.max(dp[j] + 1,dp[i]);
            }

            ret = ret > dp[i] ? ret : dp[i];
        }

        return ret;
    }
}

注意:

子序列问题相较于子数组问题的解题过程多了一层for循环,正是因为子序列问题的不连续的特性,所以要遍历i位置之前的所有子序列,时间复杂度相较于子数组问题也更高

2.摆动序列

链接:摆动序列

分析:

  1. 状态表示:根据经验很容易想到这道题的状态表示是以i位置为结束的摆动序列的最长子序列的的长度,但是在分析接下来的状态转移方程时,发现dp[i]的状态是收到前一个数的差值的正负所影响的,如果前面的差值是负,则nums[i]与前一序列结尾的数字nums[j]的差值必须为正,反之依然,所以仅仅通过一个状态转移方程是不够的,我们需要创建出两个状态转移方程表

    f[i]:以i位置为结尾,nums[i]与nums[j]的差值为的最长子序列的长度
    g[i]:以i位置为结尾,nums[i]与nums[j]的差值为的最长子序列的长度

2.状态转移方程:
在这里插入图片描述

  1. 初始化:最次状态为1,所以两个表都初始化为1
  2. 填表:从左往右
  3. 返回值:两个表的最大值

代码:

class Solution {
    public int wiggleMaxLength(int[] nums) {
        int n = nums.length;

        int[] f = new int[n];// 差值为正数的dp表
        int[] g = new int[n];// 差值为负数的dp表

        for(int i = 0; i < n; i++) f[i] = g[i] = 1;
        int ret = f[0];

        for(int i = 1; i < n; i++) {
            for(int j = 0; j < i; j++) {
                if(nums[i] - nums[j] > 0) f[i] = Math.max(g[j] + 1,f[i]);
                else if(nums[i] - nums[j] < 0) g[i] = Math.max(f[j] + 1,g[i]);
            }

            ret = Math.max(f[i],g[i]);// 更新最大值
        }

        return ret;
    }
}

3.最长定差子序列

链接:最长定差子序列

分析:

  • 笔者一拿到本题就想到了最长递增子序列那道题目,只不过这里的增加的条件变为了nums[i] - nums[j] == difference,但是大致的过程是相同的,笔者兴冲冲的C,V结果发现超时(小丑了)
  • 优化策略:超时代码中时间复杂度最高的地方在于需要对j从0位置一直遍历到i - 1,再加上外层循环,时间复杂度达到O(N^2),所以需要优化这里的寻找过程
  • 这个寻找过程的目的是找到最长的定差子序列,但是实际上没有必要从0开始找,如果有两个重复的nums[j],我们只需要取其中对应的dp[j]较大的值即可.而且,我们在遍历nums[i]的过程中,可以将每次遍历得到的nums[i]和dp[i]存入到哈希表之中,这样再回头去找最长的子序列时,只需寻找key = a - difference即可,这样搜索的时间复杂度就降低到O(1)

超时代码

class Solution {
    public int longestSubsequence(int[] nums, int difference) {
        int n = nums.length;
        int[] dp = new int[n];

        for(int i = 0 ; i < n; i++) dp[i] = 1;// 初始化dp表

        int ret = dp[0];// 记录最值

        // 填表
        for(int i = 1; i < n; i++) {
            for(int j = 0; j < i; j++) {
                if(nums[i] - nums[j] == difference)
                    dp[i] = dp[i] > dp[j] + 1 ? dp[i] : dp[j] + 1;
            }

            ret = ret > dp[i] ? ret : dp[i];
        }

        return ret;
    }
}

优化:

class Solution {
    public int longestSubsequence(int[] arr, int difference) {
        Map<Integer,Integer> hash = new HashMap<>();
        int ret = 1;// 记录最值

        for(int a : arr) {
            hash.put(a,hash.getOrDefault(a-difference, 0 ) + 1);// 将当前位置插入到哈希表中
            ret = Math.max(ret,hash.get(a));// 更新最值
        }

        return ret;
    }
}

4.最长数对链

链接:最长数对链

分析:

  • 本题其实和最长递增子序列很像,只不过这里换成了数对,需要注意的是,本题的子序列是可以任意挑选的,所以需要对数组进行排序

代码:

class Solution {
    public int findLongestChain(int[][] nums) {
        // 预处理数组 --转化为子序列问题
        Arrays.sort(nums,(a,b) -> {return a[0] - b[0];});// 此处使用的lambda表达式

        // 下面的思路和"最长递增子序列长度"相同
        int n = nums.length;
        int[] dp = new int[n];
        for(int i = 0; i < n; i++) {
            dp[i] = 1;
        }

        int ret = dp[0];
        for(int i = 1; i < n; i++) {
            for(int j = 0; j < i; j++) {
                if(nums[i][0] > nums[j][1]) {
                    dp[i] = Math.max(dp[i],dp[j] + 1);
                }
            }

            ret = ret > dp[i] ? ret : dp[i];
        }
    
        return ret;
    }
}

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

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

相关文章

微信怎样群发更高效?

群发是指通过微信平台对特定受众进行大规模信息发布的过程&#xff0c;如节日祝福、活动促销等。随着科技的不断发展&#xff0c;群发的定义已不再仅限于手机信息群发或短信群发。如今&#xff0c;微信内置的群发功能也被广泛应用。 一、微信群发的操作步骤 1. 进入微信&…

C++入门(下)

文章目录 1:引用1.1:引用概念1.2:引用的特性.1.2.1:引用在定义时必须初始化1.2.2:一个变量可以有多个引用1.2.3:引用一旦引用一个实体,再不能引用其他实体. 1.3:应用场景1.3.1:做参数1.3.2:做返回值1.3.2.1:传值返回1.3.2.2:传引用返回(错误示范)1.3.2.3:传引用返回(正确示范) …

Shell脚本学习-if循环

最小化的if语句 无实际用途 if [ ] ;then echo fi 脚本解释 if 判断 [ ] 里面的条件是否成立 后面跟then&#xff0c;代表条件成立 如果在一行则使用分号隔离&#xff08;;&#xff09; 如果不在一行使用则直接在下一行驶入then即可。 如果条件成立则输出echo 后面…

鸿蒙Harmony应用开发—ArkTS-全局UI方法(日期滑动选择器弹窗)

根据指定的日期范围创建日期滑动选择器&#xff0c;展示在弹窗上。 说明&#xff1a; 该组件从API Version 8开始支持。后续版本如有新增内容&#xff0c;则采用上角标单独标记该内容的起始版本。 本模块功能依赖UI的执行上下文&#xff0c;不可在UI上下文不明确的地方使用&…

zabbix企业微信的告警媒介配置

简介&#xff1a; Zabbix企业微信告警媒介可用于向特定群组成员发送提醒通知。 前提条件&#xff1a; 完成Zabbix告警平台的搭建后&#xff0c;需将群机器人添加至告警提醒群中。 企业微信群聊——右上角三个点——添加群机器人 保存好产生的webhook地址&#xff08;注意&…

GESP图形化编程一级认证真题 2024年3月

GESP 图形化一级试卷 &#xff08;满分&#xff1a;100 分 考试时间&#xff1a;120 分钟&#xff09; 一、单选题&#xff08;每题 3 分&#xff0c;共 30 分&#xff09; 1、小杨的父母最近刚刚给他买了一块华为手表&#xff0c;他说手表上跑的是鸿蒙&#xff0c;这个 鸿蒙是…

jQuery 基础

文章目录 1. jQuery 概述1.1 JavaScript 库1.2 jQuery 概念1.3 jQuery 优点 2. jQuery 基本使用2.1 下载2.2 使用步骤2.3 jQuery 的入口函数2.4 jQuery 的顶级对象 $2.5 DOM 对象和 jQuery 对象DOM 对象和 jQuery 对象相互转换方法 1. jQuery 概述 1.1 JavaScript 库 1.2 jQue…

【论文阅读】基于多特征融合的智能合约缺陷检测方法

摘要&#xff1a; 1、预处理&#xff1a;颜色标记、词汇提取、字符转换、合约之间的继承关系的提取 2、 使用融合模型进行特征提取&#xff08;BERT、CNN、BiLSTM&#xff09; 3、使用node2vec随机游走算法&#xff0c;将合约之间的继承关系作为输入得到合约关系的特征向量。 4…

python-多参数-放置原则

python-多参数-操作原则&#xff1a; 形参、 位置参数、可变参数居于前&#xff0c;关键字参数居中&#xff0c;可变关键字放到最后 def school(name,location,*args,date_fauned,**kwargs):print(kwargs) school("sss","woshi","mike","…

【openCV】手写算式识别

OpenCV 机器学习库提供了一系列 SVM 函数和类来实现 SVM 模型的训练和预测&#xff0c;方便用户实现自己的 SVM 模型&#xff0c;并应用于分类问题。本文主要介绍使用 openCV 实现手写算式识别的工作原理与实现过程。 目录 1 SVM 模型 1.1 SVM 模型介绍 1.2 SVM 模型原理 2…

使用广播信道的数据链路层

目录 一、局域网的特点 二、媒体共享技术 三、以太网的两个标准 四、以太网 五、CSM/CD协议 1、碰撞检测 2、争用期 3、CSMA/CD重要特性 4、CSMA/CD协议的要点 六、小结 一、局域网的特点 局域网具有如下主要优点&#xff1a; • 具有广播功能&#xff0c; 从一…

Linux系统Docker安装Drupal并配置数据库实现公网远程访问本地站点

文章目录 前言1. Docker安装Drupal2. 本地局域网访问3 . Linux 安装cpolar4. 配置Drupal公网访问地址5. 公网远程访问Drupal6. 固定Drupal 公网地址 前言 Dupal是一个强大的CMS&#xff0c;适用于各种不同的网站项目&#xff0c;从小型个人博客到大型企业级门户网站。它的学习…

【07】进阶html5

HTML5 包含两个部分的更新,分别是文档和web api 文档 HTML5 元素表 元素语义化 元素语义化是指每个 HTML 元素都代表着某种含义,在开发中应该根据元素含义选择元素 元素语义化的好处: 利于 SEO(搜索引擎优化)利于无障碍访问利于浏览器的插件分析网页新增元素 多媒体…

Spring6--基础概念

1. 概述 1.1. Spring是什么 Spring 是一套广泛应用于 Java 企业级应用开发领域的轻量级开源框架&#xff0c;由 Rod Johnson 创立&#xff0c;旨在显著降低 Java 企业应用的复杂性&#xff0c;缩短开发周期&#xff0c;并提升开发效率。Spring 不仅适用于服务器端开发&#x…

Lenze伦茨8400变频器E84A L-force Drives 操作使用说明

Lenze伦茨8400变频器E84A L-force Drives 操作使用说明

html5cssjs代码 035 课程表

html5&css&js代码 035 课程表 一、代码二、解释基本结构示例代码常用属性样式和装饰响应式表格辅助技术 一个具有亮蓝色背景的网页&#xff0c;其中包含一个样式化的表格用于展示一周课程安排。表格设计了交替行颜色、鼠标悬停效果以及亮色表头&#xff0c;并对单元格设…

关于alias、root的用法

关于alias、root的用法 root 语法&#xff1a;root path 默认值&#xff1a; root html 配置段&#xff1a; http,server,location,if 例子&#xff1a; 静态文件地址&#xff1a;/home/static/html/js/demo.html 用例1&#xff1a; 以请求http://example.com/js/demo.html为…

指路明灯,99%自动化测试从业者都该看的职业规划!

这篇文章将从以下三个方面来给大家介绍自动化测试&#xff0c;其中包含自动化测试从业者需要了解的知识和一些常见的思想误区&#xff0c;以及自动化测试行业的前景以及如何进阶 1.自动化测试的介绍&#xff1a; 自动化测试什么是&#xff0c;有哪些被称作自动化测试&#xf…