LeedCode刷题---子数组问题

顾得泉:个人主页

个人专栏:《Linux操作系统》  《C/C++》  《LeedCode刷题》

键盘敲烂,年薪百万!


一、最大子数组和

题目链接:最大子数组和 

题目描述

       给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和

       子数组 是数组中的一个连续部分

示例 1:

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

示例 2:

输入:nums = [1]
输出:1

示例 3:

输入:nums = [5,4,-1,7,8]
输出:23

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104

解法

1.状态表示:

       对于线性dp,我们可以用「经验+题目要求」来定义状态表示:

       i.以某个位置为结尾,巴拉巴拉;

       ii.以某个位置为起点,巴拉巴拉。

       这里我们选择比较常用的方式,以「某个位置为结尾」

结合「题目要求」,定义一个状态表示:

       dp[i]表示:以i位置元素为结尾的「所有子数组」中和的最大和

2.状态转移方程:

       dp[i]的所有可能可以分为以下两种

        i.子数组的长度为1:此时dpLi= nums[i];

        ii.子数组的长度大于1:此时 dp[i]应该等于以i - 1做结尾的「所有子数组」中和的最大值再加上nums[i],也就是dp[i - 1]+ nums[i]

       由于我们要的是最大值,因此应该是两种情况下的最大值,因此可得转移方程:

       dp[i] = max (nums[i],dp[i - 1]+ nums[i])

3.初始化:

       可以在最前面加上一个「辅助结点」,帮助我们初始化。使用这种技巧要注意两个点:

       i.辅助结点里面的值要「保证后续填表是正确的」;

       ii.「下标的映射关系」

       在本题中,最前面加上一个格子,并且让dp[0] = 即可

4.填表顺序:

       根据「状态转移方程」易得,填表顺序为「从左往右」

5.返回值:

       状态表示为「以1为结尾的所有子数组」的最大值,但是最大子数组和的结尾我们是不确定的。因此我们需要返回整个dp表中的最大值

代码实现

class Solution {
public:int maxSubArray(vector<int>& nums) {int n = nums.size();vector<int> dp(n + 1);int ret = INT_MIN;for(int i = 1; i <= n; i++){dp[i] = max(nums[i - 1], dp[i - 1] + nums[i - 1]);ret = max(ret, dp[i]);}return ret;}
};

二、环形子数组的最大和

题目链接:环形子数组的最大和

题目描述

       给定一个长度为 n 的环形整数数组 nums ,返回 nums 的非空 子数组 的最大可能和 

       环形数组 意味着数组的末端将会与开头相连呈环状。形式上, nums[i] 的下一个元素是 nums[(i + 1) % n] , nums[i] 的前一个元素是 nums[(i - 1 + n) % n] 

       子数组 最多只能包含固定缓冲区 nums 中的每个元素一次。形式上,对于子数组 nums[i], nums[i + 1], ..., nums[j] ,不存在 i <= k1, k2 <= j 其中 k1 % n == k2 % n 

示例 1:

输入:nums = [1,-2,3,-2]
输出:3
解释:从子数组 [3] 得到最大和 3

示例 2:

输入:nums = [5,-3,5]
输出:10
解释:从子数组 [5,5] 得到最大和 5 + 5 = 10

示例 3:

输入:nums = [3,-2,2,-3]
输出:3
解释:从子数组 [3] 和 [3,-2,2] 都可以得到最大和 3

提示:

  • n == nums.length
  • 1 <= n <= 3 * 104
  • -3 * 104 <= nums[i] <= 3 * 104

解法

       本题与「最大子数组和」的区别在于,考虑问题的时候不仅要分析「数组内的连续区域」,还要考虑「数组首尾相连」的一部分。结果的可能情况分为以下两种:

       i.结果在数组的内部,包括整个数组;

       ii.结果在数组首尾相连的一部分上

       其中,对于第一种情况,我们仅需按照「最大子数组和」的求法就可以得到结果,记为 fmax。对于第二种情况,我们可以分析一下:

       i.如果数组首尾相连的一部分是最大的数组和,那么数组中间就会空出来一部分;

       ii.因为数组的总和sum是不变的,那么中间连续的一部分的和一定是最小的;因此,我们就可以得出一个结论,对于第二种情况的最大和,应该等于sum - gmin

       其中,gmin表示数组内的「最小子数组和」

       两种情况下的最大值,就是我们要的结果

       但是,由于数组内有可能全部都是负数,第一种情况下的结果是数组内的最大值(是个负数),第二种情况下的 gmin == sum,求的得结果就会是0。若直接求两者的最大值,就会是0。但是实际的结果应该是数组内的最大值。对于这种情况,我们需要特殊判断一下

       由于「最大子数组和」的方法已经讲过,这里只提一下「最小子数组和」的求解过程,其实与「最大子数组和」的求法是一致的。用f表示最大和,g表示最小和

1.状态表示:

       g[i]表示:以i做结尾的所有子数组中和的最小值

2.状态转移方程:

       g[i]的所有可能可以分为以下两种:

       i.子数组的长度为1 :此时g[i] = nums[i] ;

       ii.子数组的长度大于1∶此时 g[i应该等于以i - 1做结尾的「所有子数组」中和的最小值再加上nums[i],也就是g[i - 1] + nums[i]

       由于我们要的是最小子数组和,因此应该是两种情况下的最小值,因此可得转移方程:

       g[i] = min( nums[i],g[i - 1]+ nums[i])

3.初始化:

       可以在最前面加上一个辅助结点,帮助我们初始化。使用这种技巧要注意两个点:

       i.辅助结点里面的值要保证后续填表是正确的;

       ii.下标的映射关系

       在本题中,最前面加上一个格子,并且让g[0]= 0即可

4.填表顺序:

根据状态转移方程易得,填表顺序为「从左往右」

5.返回值:

        a.先找到f表里面的最大值->fmax ;

        b.找到g表里面的最小值-> gmin ;

        c.统计所有元素的和->sum ;

        d.返回sum == gmin ? fmax : max ( fmax, sum - gmin)

代码实现

class Solution {
public:int maxSubarraySumCircular(vector<int>& nums) {int n = nums.size();vector<int> f(n + 1), g(n + 1);int fmax = INT_MIN, gmin = INT_MAX, sum = 0;for(int i = 1; i <= n; i++){int x = nums[i - 1];f[i] = max(x, x + f[i - 1]);fmax = max(fmax, f[i]);g[i] = min(x, x + g[i - 1]);gmin = min(gmin, g[i]);sum += x;}return sum == gmin ? fmax : max(fmax, sum - gmin);}
};

三、乘积最大子数组

题目链接:乘积最大子数组

题目描述

       给你一个整数数组 nums ,请你找出数组中乘积最大的非空连续子数组(该子数组中至少包含一个数字),并返回该子数组所对应的乘积

       测试用例的答案是一个 32-位 整数

       子数组 是数组的连续子序列

示例 1:

输入: nums = [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6

示例 2:

输入: nums = [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组

提示:

  • 1 <= nums.length <= 2 * 104
  • -10 <= nums[i] <= 10
  • nums 的任何前缀或后缀的乘积都 保证 是一个 32-位 整数

解法

       这道题与「最大子数组和」非常相似,我们可以效仿着定义一下状态表示以及状态转移:

       i. dp[i]表示以i为结尾的所有子数组的最大乘积,

       ii. dp[i] = max ( nums[i],dp[i - 1]* nums[i]);

       由于正负号的存在,我们很容易就可以得到,这样求dp[i的值是不正确的。因为 dp[i -1]的信息并不能让我们得到dp[i]的正确值。比如数组[-2,5,-2],用上述状态转移得到的dp数组为[-2,5,-2],最大乘积为5。但是实际上的最大乘积应该是所有数相乘,结果为20

       究其原因,就是因为我们在求dp[2]的时候,因为nums[2]是一个负数,因此我们需要的是「 i - 1位置结尾的最小的乘积(-10)」,这样一个负数乘以「最小值」,才会得到真实的最大值。因此,我们不仅需要一个「乘积最大值的dp表」,还需要一个「乘积最小值的dp表」

1.状态表示:

       f[i]表示:以i结尾的所有子数组的最大乘积

       g[i]表示:以i结尾的所有子数组的最小乘积

2.状态转移方程:

       遍历每一个位置的时候,我们要同步更新两个dp数组的值。

       对于f[i],也就是「以为结尾的所有子数组的最大乘积」,对于所有子数组,可以分为下面三种形式:

       i.子数组的长度为1,也就是nums[i] ;

       ii.子数组的长度大于1,但nums[i] > 0,此时需要的是i - 1为结尾的所有子数组的最大乘积f[i - 1],再乘上nums[i],也就是nums[i] * f[i - 1];

       ili.子数组的长度大于1,但nums[i]<0,此时需要的是i - 1为结尾的所有子数组的最小乘积g[i - 1],再乘上nums[i],也就是nums[i] * g[i - 1];(如果nums[i] = 0,所有子数组的乘积均为0,三种情况其实都包含了)

       综上所述,f[i] = max(nums[i],max(nums[i] * f[i - 1],nums[i] * g[i -1]))。

       对于g[i],也就是「以1为结尾的所有子数组的最小乘积」,对于所有子数组,可以分为下面三种形式:

       i.子数组的长度为1,也就是nums[i];   

       ii.子数组的长度大于1),但nums[i] > 0,此时需要的是i – 1为结尾的所有子数组的最小乘积g[i - 1],再乘上nums[i],也就是nums[i] * g[i - 1];

       iii.子数组的长度大于1,但nums[i] < 0,此时需要的是i - 1为结尾的所有子数组的最大乘积f[i - 1],再乘上nums[i],也就是nums[i] * f[i - 1];

       综上所述,g[i] = min(nums[i],min(nums[i] * f[i - 1],nums[i] * g[i -1]))

       (如果nums[i] = 0,所有子数组的乘积均为0,三种情况其实都包含了)

3.初始化:

       可以在最前面加上一个辅助结点,帮助我们初始化。使用这种技巧要注意两个点:

       i.辅助结点里面的值要保证后续填表是正确的;

       ii.下标的映射关系。

       在本题中,最前面加上一个格子,并且让f[o]=g[o]=1即可

4.填表顺序:

       根据状态转移方程易得,填表顺序为从左往右,两个表一起填

5.返回值:

       返回f表中的最大值

代码实现

class Solution {
public:int maxProduct(vector<int>& nums) {int n = nums.size();vector<int> f(n + 1), g(n + 1);f[0] = g[0] = 1;int ret = INT_MIN;for(int i = 1; i <= n; i++){int x = nums[i - 1], y = f[i - 1] * nums[i - 1], z = g[i - 1] * nums[i - 1];f[i] = max(x, max(y, z));g[i] = min(x, min(y, z));ret = max(ret, f[i]);}return ret;}
};

 结语:今日的刷题分享到这里就结束了,希望本篇文章的分享会对大家的学习带来些许帮助,如果大家有什么问题,欢迎大家在评论区留言~~~ 

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【计算机组成原理】存储器知识

目录 1、存储器分类 1.1、按存储介质分类 1.2、按存取方式分类 1.3、按信息的可改写性分类 1.4、按信息的可保存性分类 1.5、按功能和存取速度分类 2、存储器技术指标 2.1、存储容量 2.2、存取速度 3、存储系统层次结构 4、主存的基本结构 5、主存中数据的存放 5.…

浅学指针(5)sizeof和strlen的进阶理解

系列文章目录 文章目录 系列文章目录前言1. sizeof和strlen的对⽐1.1 sizeofsizeof不是函数&#xff0c;是运算符 1.2 strlen1.3 sizeof 和 strlen的对⽐ 2. 数组和指针笔试题解析• sizeof(数组名)&#xff0c;sizeof中单独放数组名&#xff0c;这⾥的数组名表⽰整个数组&…

MySQL 8.2 Command Line Client闪退

原因一 服务没有打开 原因二 找不到my.ini文件 原因一的解决方法 操作1进入管理 操作2选择服务 1 2 3 操作3选择MySQL服务并打开 原因二的解决方法 查找目录中是否有my.ini文件 C:\Program Files\MySQL\MySQL Server 8.2&#xff08;一般在这个目录下&#xff09; 有时…

Apache Flink(六):Apache Flink快速入门 - Flink案例实现

🏡 个人主页:IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 🚩 私聊博主:加入大数据技术讨论群聊,获取更多大数据资料。 🔔 博主个人B栈地址:豹哥教你大数据的个人空间-豹哥教你大数据个人主页-哔哩哔哩视频 目录

2023/12/3总结

RabbitMq 消息队列 下载地址RabbitMQ: easy to use, flexible messaging and streaming — RabbitMQ 使用详情RabbitMQ使用教程(超详细)-CSDN博客 实现延迟队列&#xff08;为了实现订单15分钟后修改状态&#xff09; 1 死信队列 当一个队列中的消息满足下列情况之一时&…

【C#】接口定义和使用知多少

给自己一个目标&#xff0c;然后坚持一段时间&#xff0c;总会有收获和感悟&#xff01; 最近在封装和参考sdk时&#xff0c;看到一个不错的写法&#xff0c;并且打破自己对接口和实现类固定的观念&#xff0c;这也充分说明自己理解掌握的知识点还不够深。 目录 前言一、什么是…

【C++】类与对象(中)

目录 1. 类的6个默认成员函数 2. 构造函数 2.1 概念 2.2 特性 3. 析构函数 3.1 概念 3.2 特性 4. 拷贝构造函数 4.1 概念 4.2 特征 5. 赋值运算符重载 5.1 运算符重载 5.2 赋值运算符重载 5.3 前置和后置重载 6. const成员 7. 取地址及const取地址操作符重载 1.…

5_企业架构LNMP高可用负载均衡服务器

企业架构LNMP高可用负载均衡服务器之Nginx 学习目标和内容 1、能够描述负载均衡的作用 2、能够了解负载均衡常见实现方式 3、能够使用Nginx实现负载均衡 4、能够描述Nginx的常见负载均衡算法 一、背景描述及其方案设计 1、业务背景描述 时间&#xff1a;2011.6.-2013.9 发布产…

二级分类菜单及三级分类菜单的层级结构返回

前言 在开发投诉分类功能模块时&#xff0c;遇到过这样一个业务场景&#xff1a;后端需要按层级结构返回二级分类菜单所需数据&#xff0c;换言之&#xff0c;将具有父子关系的List结果集数据转为树状结构数据来返回 二级分类菜单 前期准备 这里简单复刻下真实场景中 出现的…

上门按摩APP小程序,抓住机遇创新服务新模式;

上门按摩APP小程序&#xff1a;抓住机遇&#xff0c;创新服务新模式&#xff1b; 随着现代人对生活质量要求的提高&#xff0c;上门按摩服务正成为一种新的、受欢迎的生活方式。通过APP小程序&#xff0c;用户可以轻松预约按摩服务&#xff0c;解决身体疲劳问题&#xff0c;享受…

备战春招——12.3 算法

哈希表 哈希表主要是使用 map、unordered_map、set、unorerdered_set、multi_&#xff0c;完成映射操作&#xff0c;主要是相应的函数。map和set是有序的&#xff0c;使用的是树的形式&#xff0c;unordered_map和unordered_set使用的是散列比表的&#xff0c;无序。 相应函数…

半导体封装之倒装封装 (Flip Chip)

倒装封装 &#xff08;Flipchip&#xff09;是相对于引线键合(Wire Bonding)来说的&#xff0c;之所以叫做倒装&#xff0c;是因为flip chip是正面朝下放置。倒装芯片技术是通过芯片上的凸点直接将元器件朝下互连到基板、载体或者电路板上。引线键合的连接方式是将芯片的正面朝…

unordered_map与unordered_set的实现(含迭代器)

unordered_map与unordered_set的实现 文章目录 unordered_map与unordered_set的实现前言一、问题一HashTable.h 二、问题二&问题三1.封装时如何取出key2.不同类型key如何建立对应关系 三、问题四&问题五问题四问题五 四、实现代码MyUnorderedSet.hMyUnorderedMap.hHash…

1949-2021年全国31省公路里程数据

1949-2021年全国31省公路里程数据 1、指标&#xff1a;公路里程 2、范围&#xff1a;包括31省 1978-2021年期间无缺失 3、来源&#xff1a;各省NJ、产业NJ、各省统计GB 4、指标解释&#xff1a;公路里程指报告期末公路的实际长度。 统计范围&#xff1a;包括城间、城乡间、乡…

【C语言】字符串函数strlen #strcpy #strcmp #strcat #strstr及其模拟实现

在C语言中&#xff0c;有一种特殊的数据类型&#xff0c;即字符串类型。C 并没有专门定义一个字符串类型&#xff0c;这对我们使用字符串造成了一定的麻烦。但是&#xff0c;C标准库<string.h> 中定义了各种字符串函数&#xff0c;这对于我们来说是一件值得庆幸的事情。…

node.js-连接SQLserver数据库

1.在自己的项目JS文件夹中建文件&#xff1a;config.js、mssql.js和server.js以及api文件夹下的user.js 2.在config.js中封装数据库信息 let app {user: sa, //这里写你的数据库的用户名password: ,//这里写数据库的密码server: localhost,database: medicineSystem, // 数据…

优秀编程习惯一: Git提交如何写注释

feat feat - A new feature : 一个新功能 fix fix - A bug fix : bug修复 docs docs - Documentation only changes : 仅更改文档 style style - Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) : 不影响代…

vue3 + mark.js | 实现文字标注功能

页面效果 具体实现 新增 1、监听鼠标抬起事件&#xff0c;通过window.getSelection()方法获取鼠标用户选择的文本范围或光标的当前位置。2、通过 选中的文字长度是否大于0或window.getSelection().isCollapsed (返回一个布尔值用于描述选区的起始点和终止点是否位于一个位置&…

【FPGA】Verilog:二进制并行加法器 | 超前进位 | 实现 4 位二进制并行加法器和减法器 | MSI/LSI 运算电路

Ⅰ. 前置知识 0x00 并行加法器和减法器 如果我们要对 4 位加法器和减法器进行关于二进制并行运算功能&#xff0c;可以通过将加法器和减法器以 N 个并行连接的方式&#xff0c;创建一个执行 N 位加法和减法运算的电路。 4 位二进制并行加法器 4 位二进制并行减法器 换…

vue学习笔记(八)——Vue组件-进阶(插槽、自定义指令)

一、Vue组件进阶 1.1 动态组件 多个组件使用同一个挂载点&#xff0c;并动态切换 效果如下: 1. 准备被切换的 - UserName.vue / UserInfo.vue 2个组件 2. 引入到UseDynamic.vue注册 3. 准备变量来承载要显示的"组件名" 4. 设置挂载点<component>&#xf…