【动态规划刷题 16】最长等差数列 (有难度) && 等差数列划分 II - 子序列

2023-09-21 22:53:07

1027. 最长等差数列

https://leetcode.cn/problems/longest-arithmetic-subsequence/

给你一个整数数组 nums,返回 nums 中最长等差子序列的长度。

回想一下,nums 的子序列是一个列表 nums[i1], nums[i2], …, nums[ik] ,且 0 <= i1 < i2 < … < ik <= nums.length - 1。并且如果 seq[i+1] - seq[i]( 0 <= i < seq.length - 1) 的值都相同,那么序列 seq 是等差的。

示例 1:

输入:nums = [3,6,9,12]
输出:4
解释:
整个数组是公差为 3 的等差数列。
示例 2:

输入:nums = [9,4,7,2,10]
输出:3
解释:
最长的等差子序列是 [4,7,10]。
示例 3:

输入:nums = [20,1,15,3,10,5,8]
输出:4
解释:
最长的等差子序列是 [20,15,10,5]。

1.状态表示*
我们先试着定义一个状态转移数组:
dp[i] 表⽰:以 i 位置元素为结尾的「所有⼦序列」中,最⻓的等差序列的⻓度。
但是这⾥有⼀个⾮常致命的问题,那就是我们⽆法确定 i 结尾的等差序列的样⼦。这样就会导致我们⽆法推导状态转移⽅程,因此我们定义的状态表⽰需要能够确定⼀个等差序列:
dp[i][j] 表⽰:以 i 位置以及 j 位置的元素为结尾的所有的⼦序列中,最⻓的等差序列的⻓度。

2.状态转移方程
设 nums[i] = b, nums[j] = c ,那么这个序列的前⼀个元素就是 a = 2 * b - c 。我们
根据 a 的情况讨论:

  1. a 存在,下标为 k ,并且 a < b :此时我们需要以 k 位置以及 i 位置元素为结尾的最⻓等差序列的⻓度,然后再加上 j 位置的元素即可。于是dp[i][j] = dp[k][i] +1 。这⾥因为会有许多个 k ,我们仅需离 i 最近的 k 即可。因此任何最⻓的都可以以 k 为结尾;
  2. a 存在,但是 b < a < c :此时只能两个元素⾃⼰玩了, dp[i][j] = 2 ;
  3. a 不存在:此时依旧只能两个元素⾃⼰玩了, dp[i][j] = 2 ;

优化:
⼀边 dp ,⼀边保存。这种⽅式,我们仅需保存最近的元素的下标,不⽤保存下标数组。但是⽤这种⽅法的话,我们在遍历顺序那⾥,先固定倒数第⼆个数,再遍历倒数第⼀个数。这样就可以在 i 使⽤完时候,将 nums[i] 扔到哈希表中。

3. 初始化
根据实际情况,可以将所有位置初始化为 2 。

4. 填表顺序
a. 先固定倒数第⼆个数;
b. 然后枚举倒数第⼀个数。

5. 返回值
由于不知道最⻓的结尾在哪⾥,因此返回 dp 表中的最⼤值。

代码:

 int longestArithSeqLength(vector<int>& nums) {
        int n=nums.size();
        unordered_map<int,int> hash;
        hash[nums[0]]=0;

        //表示的是以 i,j 为结尾的,最长的等差子序列的长度
        int Max=2;
        vector<vector<int>>  dp(n,vector<int>(n,2));
        for(int i=0;i<n;i++)//先固定倒数第二个位置
        {
            for(int j=i+1;j<n;j++)//在固定倒数第一个位置
            {
                int a=2*nums[i]-nums[j];
                if(hash.count(a)&&hash[a]<i)
                   dp[i][j]=dp[hash[a]][i]+1;

                 Max=max(Max,dp[i][j]);
            }
            hash[nums[i]]=i;
        }
        return Max;

    }

在这里插入图片描述

446. 等差数列划分 II - 子序列

https://leetcode.cn/problems/arithmetic-slices-ii-subsequence/

给你一个整数数组 nums ,返回 nums 中所有 等差子序列 的数目。

如果一个序列中 至少有三个元素 ,并且任意两个相邻元素之差相同,则称该序列为等差序列。

例如,[1, 3, 5, 7, 9]、[7, 7, 7, 7] 和 [3, -1, -5, -9] 都是等差序列。
再例如,[1, 1, 2, 5, 7] 不是等差序列。
数组中的子序列是从数组中删除一些元素(也可能不删除)得到的一个序列。

例如,[2,5,10] 是 [1,2,1,2,4,1,5,10] 的一个子序列。
题目数据保证答案是一个 32-bit 整数。

示例 1:

输入:nums = [2,4,6,8,10]
输出:7
解释:所有的等差子序列为:
[2,4,6]
[4,6,8]
[6,8,10]
[2,4,6,8]
[4,6,8,10]
[2,4,6,8,10]
[2,6,10]
示例 2:

输入:nums = [7,7,7,7,7]
输出:16
解释:数组中的任意子序列都是等差子序列。

1.状态表示*
我们先试着定义一个状态转移数组:
dp[i] 表⽰:以 i 位置元素为结尾的「所有⼦序列」中,最⻓的等差序列的⻓度。
但是这⾥有⼀个⾮常致命的问题,那就是我们⽆法确定 i 结尾的等差序列的样⼦。这样就会导致我们⽆法推导状态转移⽅程,因此我们定义的状态表⽰需要能够确定⼀个等差序列:
dp[i][j] 表⽰:以 i 位置以及 j 位置的元素为结尾的所有的⼦序列中,等差⼦序列的个数。

2.状态转移方程
设 nums[i] = b, nums[j] = c ,那么这个序列的前⼀个元素就是 a = 2 * b - c 。我们
根据 a 的情况讨论:

  1. a 存在,下标为 k ,并且 a < b :此时我们需要以 k 位置以及 i 位置元素为结尾的最⻓等差序列的⻓度,然后再加上 j 位置的元素即可。于是dp[i][j] = dp[k][i] +1 。这⾥因为会有许多个 k ,我们仅需离 i 最近的 k 即可。因此任何最⻓的都可以以 k 为结尾;
  2. b. 因为 a 可能有很多个,我们需要全部累加起来

综上, dp[i][j] += dp[k][i] + 1 。

优化:
优化点:我们发现,在状态转移⽅程中,我们需要确定 a 元素的下标。因此我们可以在 dp 之前,将
所有元素 +下标数组绑定在⼀起,放到哈希表中。这⾥为何要保存下标数组,是因为我们要统计个
数,所有的下标都需要统计。
3. 初始化
刚开始是没有等差数列的,因此初始化 dp 表为 0
4. 填表顺序
a. 先固定倒数第⼆个数;
b. 然后枚举倒数第⼀个数。

5. 返回值
我们要统计所有的等差⼦序列,因此返回 dp 表中所有元素的和。

代码:

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

        int sum=0;
        vector<vector<int>> dp(n,vector<int>(n));
        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 e:hash[a])
                        {
                            if(e<i) { dp[i][j]+=dp[e][i]+1;}
                            else break;
                        }
                }
                sum+=dp[i][j];
            }
        }
        return sum;

    }

在这里插入图片描述

更多推荐

第三、四、五场面试

第三场共享屏幕做题(三道简单题)替换空格成%20(双指针)删除升序链表中的重复元素(指针)有效的括号(栈)第四场、第五场自我介绍项目拷打整个项目架构rpc模块的情况分析的数据从那里获取,如何获取整个项目还有哪些不足与改进docker模块的主要工作说一下DNSmap底层的红黑树跟二叉搜索树有什么区别?介绍一下HTTP介绍

DataSheet专业名词解读——每天10个专业名词(1)23.9.18 (NXP)MPC5604B/C

文章目录1.variablelengthencoding(VLE)可变长度编码2.ErrorCorrectionCode(ECC)纠错编码3.Memoryprotectionunit(MPU)内存保护单元4.Interruptcontroller(INTC)中断控制器5.Frequencymodulatedphase-

机器视觉-标定篇

3D结构光标定结构光视觉的优点:非接触、信息量大、测精度高、抗干扰能力强。结构光视觉传感器参数的标定包括:摄像机参数标定、结构光平面参数标定。结构光视觉测量原理图我们不考虑镜头的畸变,将相机的成像模型简化为小孔成像模型,则特征点的图像坐标Pf与其在摄像机坐标系下的三维坐标P之间的关系可表示为:其中:(u,v)是特征点的

在Vue中使用Immutable.js

在Vue3中使用Immutable.js以下是如何在Vue.js中使用Immutable.js的步骤:首先,需要安装immutable.js。你可以通过npm或yarn来安装:npminstallimmutable或者yarnaddimmutable在你的Vue组件中导入Immutable:import{Map,Lis

OOM问题排查解决方案、Arthas分析高CPU问题

一、OOM问题分析流程:第一步:进程分析,分析老年代回收次数和消耗时间第二步:日志分析,找出OOM发生时间的日志来锁定执行方法,对应的机器ip第三步:找到对应的ip机器查看,进一步分析第四步:下载的dump,使用mat分析堆内存,找到堆占用率前3,查看堆指向问题产生:查看新生代最高1000M,如果大数据量调用,jvm会

TypeScript逆变 :条件、推断和泛型的应用

TypeScript逆变:条件、推断和泛型的应用1一个类型问题有一个名为test的函数,它接受两个参数。第一个参数是函数fn,第二个参数options受到fn参数的限制。乍一看,这个问题貌似并不复杂,不是吗?糊业务的时候,这种不是常见的需求嘛。“创建一个泛型类型Test,以确保这两个参数之间存在约束关系就完事了,睡醒再

Vue3 - 实现动态获取菜单路由和按钮权限控制指令

GitHubDemo地址在线预览前言关于动态获取路由已在这里给出方案Vue-vue-admin-template模板项目改造:动态获取菜单路由这里是在此基础上升级成vue3和ts,数据和网络请求是通过mock实现的具体代码请看demo!!!本地权限控制,具体是通过查询用户信息获取用户角色,在路由守卫中通过角色过滤本地配

Postgresql JIT README翻译

WhatisJust-in-TimeCompilation?=================================Just-in-Timecompilation(JIT)istheprocessofturningsomeformofinterpretedprogramevaluationintoanativ

Linux基础指令(四)

目录前言1.find&which指令1.1find1.2which1.3alias1.4where2、grep指令3、xargs指令结语:前言欢迎各位伙伴来到学习Linux指令的第四天!!!在上一篇文章Linux基本指令(三)当中,我们学会了通过学习echo指令,引入了Linux系统中,输出重定向、追加重定向、输入重定

基于海康Ehome/ISUP接入到LiveNVR实现海康摄像头、录像机视频统一汇聚,做到物联网无插件直播回放和控制

LiveNVR支持海康NVR摄像头通EHOME接入ISUP接入LiveNVR分发视频流或是转GB281811、海康ISUP接入配置2、海康设备接入2.1、海康EHOME接入配置示例2.2、海康ISUP接入配置示例3、通道配置3.1、直播流接入类型海康ISUP3.2、海康ISUP设备ID3.3、启用保存3.4、接入成功4

java---jar详解

一、helpC:\Users\lichf1>jar用法:jar{ctxui}[vfmn0PMe][jar-file][manifest-file][entry-point][-Cdir]files...选项:-c创建新档案-t列出档案目录-x从档案中提取指定的(或所有)文件-u更新现有档案-v在标准输出中生成详细输出-

热文推荐