数据结构与算法-单调栈1

news2025/1/11 6:51:07

先介绍一下单调栈是什么

一种特别设计的栈结构,为了解决如下的问题:

给定一个可能含有重复值的数组arri位置的数一定存在如下两个信息

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

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

如果想得到arr中所有位置的两个信息,怎么能让得到信息的过程尽量快。

如果用暴力方法每个位置都去看看左边和右边第一个比它小的数是哪个,那时间复杂度就是O(N^2)了,而单调栈的的每个元素都只有一次入栈和出栈的过程,也就是说时间复杂度是O(2N),省略常数时间O(N)

我们来解析一下[3,4,2,6,7,1,0,5]的入栈和出栈过程

本题单调栈原则:栈中元素从小到上越来越大

对于每一个数:如果栈顶元素小于他,直接入栈。如果栈顶元素大于它弹出栈顶,然后继续和下一个栈顶比较,弹出的时候计算弹出元素的左边第一个比它小的(它压着的那个)和右边第一个比它小的(把他弹出那个)。

问题1:为什么右边第一个比它小的是把它弹出的那个?我们假设C入栈的时候要弹出B,那么C肯定是小于B的且我们是根据数组的顺序入栈的,C在原来数组中一定在B的右边,那C和B之间有没有可能有其他小于B的元素呢?不可能的,如果有的话就把B弹出了,轮不到C来弹出B

问题2:为什么左边第一个比它小的是它压着的那个?假设B下面压着A,那数组中A肯定在B的左边,因为B压在A的上面,所以A一定比B小,否则B入栈的时候A就弹出了,那A和B之间有没有可能有其他小于B的元素呢?不可能的,如果有其他小于B的元素,理论上讲有两种可能(如果存在假设为X):1.X大于A小于B,那B下面压着的应该是X,而我们现在B下面压着A  2.X小于B且X小于A,那X入栈的时候A就应该弹出了,现在A没弹出,说明这种可能性不存在

 最后把数组中的元素单独弹出:

(1)右边比它小的不存在,这个好解释,如果右边有比它小的,那他就不会在栈里,而应该被弹出了。

(2)左边第一个比它小的就是它压着的,这个参考下上面的问题2

以上讨论的是无重复的情况,下面讨论一下有重复值的情况

如果有重复值的话,我们把结构变更为栈中每个元素都是链表

 左边比它小的是它压着那个链表的最后一个位置(只有一个就是那个位置)

好吧,接着上代码

package dataStructure.danDiaoZhan;

import java.util.ArrayList;
import java.util.List;
import java.util.Stack;

public class GetNearLess {
    public static int[][] getNearLessNoRepeat(int[] nums) {
        //没有元素我给你返回给鸟啊
        if(nums == null || nums.length == 0) {
            return null;
        }
        //返回值数组,lessArr[i][0]表示i位置的左边第一个比他小的数的下标,
        //lessArr[i][1]表示i位置的右边第一个比他小的数的下标,
        int[][] lessArr = new int[nums.length][2];
        //单调栈栈自底到顶依次增大的原则
        Stack<Integer> stack = new Stack<>();
        //从0到nums.length-1挨个入栈
        for(int i = 0; i < nums.length; i++) {
            //如果栈不为空并且栈顶元素大于当前元素,如果这个时候直接入栈就违反自底到顶依次增大的原则
            //先把大于当前数的都弹出再入栈
            while(!stack.isEmpty() && nums[stack.peek()] > nums[i]) {
                int popNum = stack.pop();
                //它弹出后栈是不是为空,就是它原来在栈里有没有压着其他元素,有的话左边第一个比他小的就是这个元素
                lessArr[popNum][0] = stack.isEmpty()? -1 : stack.peek();
                //右边第一个比他小的是把它弹出那个,也就是当前的i
                lessArr[popNum][1] = i;
            }
            //不符合要求的都弹出了,栈为空或者栈里的都是比当前元素小的,直接入栈
            stack.push(i);
        }
        //如果最后所有元素都入栈过之后,栈不为空,需要单独进行结算
        while(!stack.isEmpty()) {
            int popNum = stack.pop();
            //它弹出后栈是不是为空,就是它原来在栈里有没有压着其他元素,有的话左边第一个比他小的就是这个元素
            lessArr[popNum][0] = stack.isEmpty()? -1 : stack.peek();
            //单独结算这些肯定不存在右边比他小的,因为如果右边有比他小的,单独结算之前就弹出了
            lessArr[popNum][1] = -1;
        }
        return lessArr;
    }

    public static int[][] getNearLess(int[] nums) {
        //没有元素我给你返回给鸟啊
        if(nums == null || nums.length == 0) {
            return null;
        }
        //返回值数组,lessArr[i][0]表示i位置的左边第一个比他小的数的下标,
        //lessArr[i][1]表示i位置的右边第一个比他小的数的下标,
        int[][] lessArr = new int[nums.length][2];
        //单调栈栈自底到顶依次增大的原则,这个方法栈里面的元素变成了ArrayList
        //这里为什么要使用ArrayList?因为我们后面会用到很多的list.getIndex(list.size()-1)这一类的操作
        //ArrayList效率高,而LinkedList需要从头开始找到尾
        Stack<ArrayList<Integer>> stack = new Stack<>();
        //从0到nums.length-1依次入栈
        for(int i = 0; i < nums.length; i++) {
            //第一步:把栈中大于它的弹出
            while(!stack.isEmpty() && nums[stack.peek().get(0)] > nums[i]) {
                ArrayList<Integer> popList = stack.pop();
                for(int popNum : popList) {
                    //如果当前list弹出后栈里还有元素,说明它原来在栈里是压着元素的,而它压着的第一个list的最后一个元素就是它左边第一个小于它的数
                    lessArr[popNum][0] = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size() - 1);
                    lessArr[popNum][1] = i;
                }
            }
            //第二步:把自己放入栈里
            //这里需要判断一下当前元素和栈顶元素是不是相等,当然首先要判断栈里有没有元素
            if(!stack.isEmpty() && nums[i] == nums[stack.peek().get(0)]) {
                ArrayList<Integer> peekList = stack.peek();
                peekList.add(i);
            } else {
                ArrayList<Integer> newList = new ArrayList<>();
                newList.add(i);
                stack.push(newList);
            }
        }
        //单独弹出和结算的过程
        while(!stack.isEmpty()) {
            ArrayList<Integer> popList = stack.pop();
            for(int popNum : popList) {
                //如果当前list弹出后栈里还有元素,说明它原来在栈里是压着元素的,而它压着的第一个list的最后一个元素就是它左边第一个小于它的数
                lessArr[popNum][0] = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size() - 1);
                //既然还在栈里,说明它的右边没有比它小的,不然那个数入栈的时候它就被弹出了
                lessArr[popNum][1] = -1;
            }
        }
        return lessArr;
    }
    public static void main(String[] args) {
        int[] nums = {3,4,2,6,7,1,0,5};
        int[][] lessArr = getNearLessNoRepeat(nums);
        printArr2D(lessArr);
        System.out.println("=============================");
        int[] numsRepeat = {1,3,4,5,4,3,1,2};
        int[][] lessArrRepeat = getNearLess(numsRepeat);
        printArr2D(lessArrRepeat);
    }

    public static void printArr2D(int[][] arr) {
        for(int i = 0; i < arr.length; i++) {
            for(int j = 0; j < arr[i].length; j++) {
                System.out.print(arr[i][j] + "  ");
            }
            System.out.println();
        }
    }
}

注意:代码里的getNearLess方法适用于有重复和无重复的情况,而getNearLessNoRepeat只能用于无重复值的情况。代码中我把图中的链表替换为了ArrayList,原因是我们用到了大量的list.get(list.size()-1)的操作,ArrayList是根据下标寻址的,效率更高,而LinkedList是从头到尾遍历。当然LinkedList是有getLast方法的,效率是一样的,哈哈,喜欢就好,我就喜欢ArrayList

每个有疑问的地方我都加了注释,应该不难理解,有疑问可以随时私信我,保证讲解清楚

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

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

相关文章

买法拍房注意事项

1、查清法拍房房屋属性。 竞拍前需查清楚法拍房的使用年限、能否办理房产证、土地性质等。 若土地为划拨属性&#xff0c;房屋可能需补缴土地出让金&#xff0c;该费用最好提前咨询当地不动产登记中心了解。 2、产权是否涉及二次过户。 二次过户指的是房屋已经过2次交易&…

苦卷一个月,P9大佬给我的Alibaba面试手册,终于成功踹开字节大门

怎么说呢&#xff0c;今年真的是寒气逼人啊&#xff01;在这个大环境下&#xff0c;裁员已经不算是特别的事情&#xff0c;粗暴裁员也许是未来一种趋势…在职的卷的起飞&#xff0c;离职的找不到好工作。 做点能做的&#xff1a;跑跑步骑骑车多锻炼&#xff1b;当当上面正版书…

分布式全局唯一id实现-2 springCloud-MyBatis-Plus集成百度分布式全局id(uid-generator)

前言&#xff1a;MyBatis-Plus 集成百度的uid-generator &#xff0c;实现业务实体在insert 实体时&#xff0c;可以自动获取全局id&#xff0c;完成数据保存&#xff1b; 1 uid-generator 全局id 生成的方式了解&#xff1a; Snowflake算法描述&#xff1a;指定机器 & 同…

如何避免孩子独自在家偷偷使用电脑?

电脑为我们的生活带来了极大的便利&#xff0c;但是对于孩子来说&#xff0c;过早的接触网络很容易影响其健康的成长。家长在家的话&#xff0c;还可以监督孩子&#xff0c;但如果家长出门了&#xff0c;该如何避免孩子偷偷使用电脑呢&#xff1f;其实方法很简单&#xff0c;只…

网络进阶学习:交换机二层

交换机二层 交换机的概念和作用交换机的划分交换机第二层的内容⭐第一部分&#xff1a;MAC地址⭐第二部分&#xff1a;逻辑链路控制子层⭐第三部分&#xff1a;介质访问控制子层⭐第四部分&#xff1a;交换机转发表⭐第五部分&#xff1a;VLAN⭐第六部分&#xff1a;STP 交换机…

Hudi系列25: Flink SQL使用checkpoint恢复job异常

文章目录 一. 通过Flink SQL将MySQL数据写入Hudi二. 模拟Flink任务异常2.1 手工停止job2.2 指定checkpoint来恢复数据2.3 整个yarn-session上的任务恢复 三. 模拟源端异常3.1 手工关闭源端 MySQL 服务3.2 FLink任务查看 FAQ:1. checkpoint未写入数据2. checkpoint 失败3. 手工取…

自然语言处理技术简介

长期以来&#xff0c;研究人员进行自然语言处理研究主要依赖各种机器学习模型&#xff0c;以及手工设计的特征&#xff0c;但这样做带来的隐患是由于语言信息被稀疏表征表示&#xff0c;会出现维度诅咒之类的问题。而随着近年来词嵌入&#xff08;低维、分布式表征&#xff09;…

港联证券|A股船舶板块景气反转即将到来

在经历了去年的爆发后&#xff0c;2023年的中国造船业仍然处在订单交付两旺的高度景气周期之中。 5月22日&#xff0c;中国船舶集团有限公司旗下沪东中华造船&#xff08;集团&#xff09;有限公司宣布交付全球最大级别24116TEU超大型集装箱船系列3号船“地中海吉玛”号。据报道…

3D 对象转换器应该如何将 OBJ 转换为 FBX ?

Aspose.3D 是一个功能丰富的游戏软件和计算机辅助设计&#xff08;CAD&#xff09;的API&#xff0c;可以在不依赖任何3D建模和渲染软件的情况下操作文档。API支持Discreet3DS, WavefrontOBJ, FBX (ASCII, Binary), STL (ASCII, Binary), Universal3D, Collada, glTF, GLB, PLY…

SpringMVC框架理解

JavaEE体系结构包括四层&#xff0c;从上到下分别是应用层、Web层、业务层、持久层。Struts和SpringMVC是Web层的框架&#xff0c;Spring是业务层的框架&#xff0c;Hibernate和MyBatis是持久层的框架。 为什么要使用SpringMVC&#xff1f; 很多应用程序的问题在于处理业务数据…

一对一项目指导,在线购物网站webform+SQLServer技术架构

我是Tom老师&#xff0c;10开发经验&#xff0c; 我先后在携程网、陆金所&#xff0c;两家互联网和金融行业领头公司 担任高级开发工程师&#xff0c; 技术深厚&#xff0c;开发经验丰富&#xff0c;认真负责。 我现在专门做一对一编程辅导。 希望我的专业辅导&#xff0c;…

02数字图像基础

文章目录 2数字图像基础2.4图像取样和量化2.4.4图像内插 2.5像素间的一些基本关系2.5.1相邻像素2.5.2邻接性、连通性、区域和边界2.5.3距离度量 2.6 数字图像处理2.6.1阵列和矩阵操作2.6.2线性操作和非线性操作2.6.3算术操作2.6.5空间操作2.6.6向量与矩阵操作2.6.7图像变换2.6.…

架构演变之路

一)单机架构: 一)定义:应用服务和数据库服务器共用一台服务器&#xff0c;所有的服务被部署到一台服务器上面 蓝色的就是我们写的JAVA代码用户服务负责用户的登录和注册&#xff0c;商品服务用于商品的购买和交易&#xff0c;交易模块用于用户的下单和购买&#xff0c;在数据库…

周赛 Round#3 题解

又不能放图片&#xff0c;又不能写学校&#xff0c;你让我怎么办啊&#xff01;&#xff01; 系列文章目录 1.周赛 Round#1 2.周赛 Round#2 前言 这是周赛第三轮。//涉及隐私原因&#xff0c;博文里不放题目&#xff0c;要看的去http://0241:101:610:801.22222 划分字符串贪…

树状数组(一)

文章目录 前言一、树状数组简介二、树状数组的原理与相应模块三、实战演练3.1 区域和检索 - 数组可修改3.1.1 题目链接3.1.2 题目描述3.1.3 题目代码3.1.4 解题思路 3.2 数字流的秩3.2.1 题目链接3.2.2 题目描述3.2.3 题目代码3.2.4 解题思路 总结 前言 给定一段数字&#xff…

多元回归预测 | Matlab麻雀算法(SSA)优化BP神经网络回归预测,SSA-BP回归预测,多变量输入单输出模型

文章目录 效果一览文章概述部分源码参考资料效果一览 文章概述 多元回归预测 | 麻雀算法(SSA)优化BP神经网络回归预测,SSA-BP回归预测,多变量输入单输出模型 评价指标包括:MAE、RMSE和R2等,代码质量极高,方便学习和替换数据。要求2018版本及以上。 部分源码 %-------------…

Windows10家庭版安装WSL

记录Windows10家庭版安装linux子系统WSL 查看自己的windows版本是否支持安装wsl2&#xff0c;cmd里输入ver查看。 系统版本&#xff1a;Windows 10 1903及以上版本。 系统内部版本&#xff1a; 18362及以上。 启用适用于Linux的windows子系统 右键命令提示符&#xff0c;以管…

小航编程题库2022年NOC决赛图形化(小低组)(含题库教师账号)

需要在线模拟训练的题库账号请点击 小航助学编程在线模拟试卷系统&#xff08;含题库答题软件账号&#xff09;_程序猿下山的博客-CSDN博客 单选题3.0分 删除编辑 答案:C 第1题对小猫编程&#xff0c;程序运行后&#xff0c;看到的小猫最终方向是多少&#xff1f; A、120B…

Compose二三事:初步认识

Compose 是什么&#xff1f; Compose是Jetpack系列中用于构建原生Android界面的工具库&#xff0c;Jetpack是Google推出的一系列帮助开发者规范代码的库。简单来说就是用代码写UI&#xff0c;也就是声明式UI。 声明式UI和命令式UI的区别在于&#xff0c;声明式UI更关心做什么&…

Python系列模块之标准库re详解

感谢点赞和关注 &#xff0c;每天进步一点点&#xff01;加油&#xff01; 目录 一、Python 正则表达式 1.1 re模块常用操作 1.2 re.match 1.3 re.search 1.4 re.findall 1.5 re.compile 函数 1.6 re.sub 检索和替换 1.7 re.split拆分 1.8 实战案例&#xff1a;根据文…