单调栈结构

单调栈
单调栈是一种特殊设计的栈结构,为了解决如下的问题:
给定一个可能含有重复数值的 arr[],i位置的数一定存在如下两种信息:

  1. arr[i]的左侧离 i 最近并且小于(或者大于)arr[i] 的数在哪?
  2. arr[i]的右侧离 i 最近并且小于(或者大于)arr[i] 的数在哪?

如果想要得到arr[]中所有元素的这两个位置的信息,怎么能让得到信息的过程尽可能的快?

单调栈的实现

无重复值
拿左右侧距离arr[i]最近并且小于arr[i]来举例
假设当前有数组arr[] = {3,1,5,7,2,6,4},从0 ~ N-1顺序遍历。而我们的单调栈严格保持着栈底到栈顶是由小到大的顺序,此时栈中为null,所以0位置的3直接放入栈中。
在这里插入图片描述
下一个1位置1,如果此时将1放入栈中破坏了小 -> 大的栈结构,所以将0位置3弹出,弹出时收集0 - 3的左右侧距离最近的小于当前元素的信息。0 - 3弹出后栈中无元素,证明左侧没有比它小的,所以为 -1,右侧最近为1,此时弹出0->3 将 1-1放入栈中。
在这里插入图片描述
继续向下,数组下标2位置的5和3位置的7都可以保证栈由小到大的顺序,所以按照顺序放入栈中即可。此时数组下标来到了4位置的2。
在这里插入图片描述
4 - 2如果想要放入栈中,会破坏由小到大的顺序,所以此时弹出栈顶元素 3 - 7,3 - 7右侧最近并且小于当前元素的值为 4 - 2,左侧最近且小的元素为当前栈顶元素 2 - 5 ,3 - 7元素左右侧信息收集完成。
此时4 - 2依然不能放入栈中,因为当前栈顶元素为2 - 5,弹出栈顶元素2 - 5,收集 2 - 5 的信息,同样的,4 - 2元素过来导致的2 - 5元素出栈,所以 4 - 2 元素为2 - 5的右侧最近且小于当前元素的值。此时栈中剩余元素 1 - 1,所以2 - 5 右侧信息 4 - 2 左侧信息 1 -1。
栈中剩余元素 1 - 1,4 - 2放入满足条件,直接入栈。
在这里插入图片描述
下一个元素 5 - 6直接入栈,来到最后一个元素 6 - 4。
在这里插入图片描述
来看6 - 4,此时栈顶元素 5 - 6,直接入栈不符合有小到大要求,所以弹出栈顶元素 5 - 6,收集 5 - 6信息,6 - 4元素的到来导致的5 - 6元素的出栈,所以6 - 4为 5 - 6的右侧最近且小的元素,此时栈顶元素4 - 2为左侧最近且小的元素,收集好5 -6信息后,6 - 4元素入栈。
在这里插入图片描述
此时数组已经遍历完了,接下来怎么办?循环弹出栈中元素,把栈弹空即可。
因为此时已经不会再有新的元素加进来,所以栈中剩余元素的右侧最近且小的值都为 - 1。
弹出 6 - 4后,栈顶元素为 4 - 2,所以 4 - 2是6 - 4左侧最近且小的元素,右侧为-1。
弹出 4 - 2后,栈顶元素为1 - 1,所以 1 - 1是 4 - 2左侧最近且小的元素,右侧为 -1。
最后剩的元素1 - 1,弹出后栈为null,所以1 - 1左右两侧都没有最近且小的值, -1 -1。
数组中所有元素左右侧信息收集完毕。
在这里插入图片描述

证明(数组中无重复值)
我们来验证一下上面所说的流程,看它为什么是对的。
假设在某一步,B下面是A,此时由于C的到来B要出栈,此时要收集B的信息。

为什么C会是B右侧最近且小的数。

  1. C为什么会使B弹出? 由于我们单调栈的特性,所以一定是因为B的值大于C,并且是从数组0位置开始到 N - 1位置遍历,所以说数组中C一定在B的右边。
  2. B和C中间的数有没有可能小于B? 也不可能,如果BC中间有小于B的数,那B早就会被弹出栈,不会等C过来的时候才出栈。所以C才是B右边最近且小于B的数。

为什么B左边比它小的是A?

  1. 因为在栈中B在A的上面,所以在数组中B一定在A的右边。
  2. A和B中间的数,有没有可能小于A? 不可能,如果有这样的数,A早就会出栈,不会B下面压着A。
  3. A和B中间的数,有没有大于A 小于B? 也不可能,比如说A = 3 B = 7,在数组中是3 5 4 7这样的顺序。3进栈后5紧接着也会跟着进栈,但是到4的时候,5虽然出栈了,不过等7进栈时,B不会压着A,中间还会有个4,B能直接压着A说明AB中间没有 大于A小于B的数。
  4. A和B中间的数都是大于B的,B下面压着A,A在B的左边,那A不就是B左边最近且小的值么?
    在这里插入图片描述

数组中有重复值
如果数组中有重复值该怎么弄? 栈中存一个链表结构,相同值的数组下标放在一起,来看下面的图。
假设数组int[] arr = {1,3,4,5,4,3,1,2},刚来上0 - 1时,栈中为null,直接入栈,后面的3 4 5同样符合栈中元素从小到大的顺序,一次入栈。此时来到了数组下标位置为4的元素。
在这里插入图片描述
4 - 4小于当前栈顶元素 3 - 5,弹出栈顶元素,此时结算3 - 5元素的信息,因为是 4 - 4元素的到来导致我出栈的,所以我右侧最近且小的信息是4 - 4,左侧最近且小的元素为栈中下一个元素的链表中最后一个元素,在这里是2 - 4,所以3 - 5左侧最近且小的值2 - 4,右侧最近且小的值 4 - 4。还没完,此时栈顶元素2 - 4的值和4 - 4的值相等,统一放在链表中。
在这里插入图片描述
接下往下走,来到了5 - 3,此时栈顶元素大于当前值5 - 3,弹出栈顶元素,2 - 4和4 - 4两个值一起结算,因为是5 - 3的到来导致我们两个出栈,所以右侧最近且小的值为5- 3,左侧最近且小的值,依然是找栈中下一个元素的链表中的最后一个元素,在这里是1 - 3,一起结算的相同值的左右最近且最小元素一定是一样的,所以2 - 4和4 - 4左侧最近且小的值为1 - 3,右侧近且小的值为5 - 3。弹出后,5 - 3压入栈中。
在这里插入图片描述
在往下走,来到了6 - 1,同样处理,1 - 3 和 5 - 3 弹出,一起结算,左侧最近且小的值为 0 - 1,右侧最近且小的值为 6 - 1。6 - 1压入栈中。
在这里插入图片描述
最后7 - 2直接入栈。数组遍历到头了,循环遍历直接弹出栈中元素。依然是没有新的元素加入,所以栈中剩余元素所有的右侧最近且小的值为-1。
7 - 2左侧最近且小的值,看栈中下一个元素的链表中最后一个元素,在这里为 6 - 1,所以 7 - 2的左侧最近且小的值为 6 - 1,右侧最近且小为 -1。
剩余的 0 - 1元素和 6 - 1元素,左右最近且小的值都为-1。
在这里插入图片描述
数组中元素信息全部收集完成。

总结
那个元素使栈中元素弹出,即为弹出元素的右侧最近且小的值,弹出后的栈顶元素为弹出元素的左侧最近且小的值。数组长度为N,遍历完整个数组,所有元素再栈中最多进一次出一次,所以时间复杂度为 O ( N ) O(N) O(N)
如果数组中有重复值,则用链表维护相同值的元素下标。

代码
返回的二维数组中第一个数组代表arr中的下标,每个数组对应的第二个数组中一共两个值,0表示左侧最近且小,1表示右侧最近且小。
[
0 : [-1, 1]
1 : [-1, -1]
2 : [ 1, -1]
3 : [ 2, -1]
]
无重复值

 public static int[][] getNearLessNoRepeat(int[] arr) {int[][] result = new int[arr.length][2];Stack<Integer> stack = new Stack<>();for (int i = 0; i < arr.length; i++) {while (!stack.isEmpty() && arr[stack.peek()] > arr[i]) {Integer cur = stack.pop();Integer leftLessIndex = stack.isEmpty() ? -1 : stack.peek();result[cur][0] = leftLessIndex;result[cur][1] = i;}stack.push(i);}while (!stack.isEmpty()) {Integer cur = stack.pop();Integer leftLessIndex = stack.isEmpty() ? -1 : stack.peek();result[cur][0] = leftLessIndex;result[cur][1] = -1;}return result;}

有重复值

 public static int[][] getNearLess(int[] arr) {int[][] res = new int[arr.length][2];Stack<List<Integer>> stack = new Stack<>();for (int i = 0; i < arr.length; i++) {while (!stack.isEmpty() && arr[stack.peek().get(0)] > arr[i]) {List<Integer> linked = stack.pop();Integer leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size() - 1);for (Integer cur : linked) {res[cur][0] = leftLessIndex;res[cur][1] = i;}}if (!stack.isEmpty() && stack.peek().get(0) == i) {stack.peek().add(Integer.valueOf(i));} else {ArrayList<Integer> list = new ArrayList<>();list.add(i);stack.push(list);}}while (!stack.isEmpty()) {List<Integer> lists = stack.pop();Integer leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size() - 1);for (Integer cur : lists) {res[cur][0] = leftLessIndex;res[cur][1] = -1;}}return res;}

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

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

相关文章

《深入理解计算机系统》学习笔记 - 第四课 - 浮点数

Floating Point 浮点数 文章目录 Floating Point 浮点数分数二进制示例能代表的数浮点数的表示方式浮点数编码规格化值规格化值编码示例 非规格化的值特殊值 示例IEEE 编码的一些特殊属性四舍五入&#xff0c;相加&#xff0c;相乘四舍五入四舍五入的模式二进制数的四舍五入 浮…

MATLAB | 官方举办的动图绘制大赛 | 第四周(收官周)赛情回顾

MATHWORKS官方举办的迷你黑客大赛第三期(MATLAB Flipbook Mini Hack)圆满结束&#xff0c;虽然我的水平和很多大佬还有比较大的差距&#xff0c;但所有奖也算是拿满了&#xff1a; 专家评选前三名&#xff0c;以及投票榜前十&#xff1a;~ 每周的阶段性获奖者&#xff1a; 下面…

16ASM 分段和机器码

8086CPU存储分段管理 问题1&#xff1a;8086是16位cpu&#xff0c;最多可访问&#xff08;寻址&#xff09;多大内存&#xff1f; 运算器一次最多处理16位的数据。地址寄存器的最大宽度为16位。访问的最大内存为&#xff1a;216 64K 即 0000 - FFFF。 问题2&#xff1a;808…

解决方案:aarch64 ARM架构下安装Miniconda + 离线迁移Conda环境的全流程 踩坑避坑指南

目录 一、安装Miniconda1.1、确认本机架构1.2、下载Miniconda安装包1.3、安装Miniconda 二、离线配置Conda环境2.1、查看本机配置2.2、迁移虚拟环境2.3、可能遇见的报错 最后 在ARM架构下&#xff0c;由于Anaconda并不提供官方的ARM版本&#xff0c;在很多情况下强行在ARM系统中…

【图片版】计算机组成原理考前复习题【第2章 运算方法和运算器-2】

目录 前言 考前复习题&#xff08;必记&#xff09;​编辑 结尾 前言 在计算机组成原理的学习过程中&#xff0c;我们深入探索了计算机系统概述这一重要领域。计算机系统作为现代科技的核心&#xff0c;是整个计算机科学的基石。我们将学到的知识与理论转化为了能够解决现实…

为什么需要 Kubernetes,它能做什么?

传统部署时代&#xff1a; 早期&#xff0c;各个组织是在物理服务器上运行应用程序。 由于无法限制在物理服务器中运行的应用程序资源使用&#xff0c;因此会导致资源分配问题。 例如&#xff0c;如果在同一台物理服务器上运行多个应用程序&#xff0c; 则可能会出现一个应用程…

RabbitMQ学习笔记10 综合实战 实现新商家规定时间内上架商品检查

配置文件&#xff1a; 记住添加这个。 加上这段代码&#xff0c;可以自动创建队列和交换机以及绑定关系。 我们看到了我们创建的死信交换机和普通队列。 我们可以看到我们队列下面绑定的交换机。 我们创建一个controller包进行测试: 启动&#xff1a; 过一段时间会变成死信队列…

MySQL- in(集合) 和 not in(...) 的使用和练习

1. 基础用法 mysql中in常用于where表达式中&#xff0c;其作用是查询某个范围内的数据。 select * from where field in (value1,value2,value3,…) 当 IN 前面加上 NOT 运算符时&#xff0c;表示与 IN 相反的意思&#xff0c;即不在这些列表项内选择 select * from where …

API测试基础之http协议

http简介&#xff1a; http&#xff08;超文本传输协议&#xff09;是一个简单的请求-响应协议&#xff0c;它通常运行在TCP&#xff08;传输控制协议&#xff09;之上。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。请求和响应消息的头以ASCII码形式给出…

Star CCM+ 停止并保存用命令行运行的计算

在 StarCCM 命令行运行 中介绍了命令行运行计算的方法&#xff0c;有网友询问停止计算的命令&#xff0c;但计算一旦提交之后应该是不能用命令结束的&#xff0c;除非是用 kill 或任务管理器直接结束进程。然而&#xff0c;直接结束进程不会自动保存计算结果。 问题 通常情况下…

《使用ThinkPHP6开发项目》 - 创建应用

《使用ThinkPHP6开发项目》 - 安装ThinkPHP框架-CSDN博客 《使用ThinkPHP6开发项目》 - 设置项目环境变量-CSDN博客 《使用ThinkPHP6开发项目》 - 项目使用多应用开发-CSDN博客 根据前面的步骤&#xff0c;我们现在就可以开发我们的项目开发了&#xff0c;根据项目开发的需要…

轻量封装WebGPU渲染系统示例<46>- 材质组装管线(MaterialPipeline)灯光、阴影、雾以及多Pass(源码)

当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/material/src/voxgpu/sample/MaterialPipelineMultiPasses.ts 当前示例运行效果: 此示例基于此渲染系统实现&#xff0c;当前示例TypeScript源码如下&#xff1a; export class MaterialPipelin…