数据结构-单调栈1

news2024/12/27 12:46:53

先介绍一下单调栈是什么

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

给定一个可能含有重复值的数组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/546699.html

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

相关文章

【天线专题】史密斯(Smith)圆图

对一个器件进行表征时,所发生的反射大小取决于入射信号“看到的”阻抗。由于任何阻抗都能用实部和虚部(R+jX 或 G+jB )表示,故可以将他们绘制在所谓复阻抗平面的直线网络上,如下图所示。遗憾的是,开路(一种常见的射频阻抗)在实轴上表现为无限大,因而无法表示出来。 …

集合Arrary

目录 ArraryList 引用基本类型 案例1&#xff1a;定义一个集合添加学生姓名年龄 案例2&#xff1a;查看是否存在这个id 案例3&#xff1a;手机 案例4&#xff1a;学生管理系统&#xff08;不完整&#xff09; 集合长度可变&#xff1a;自动扩容集合和数据 长度存储数据类…

Riiid Answer Correctness Prediction - lgb baseline 学习

链接 特征 features [user_questions, user_mean, content_questions, content_mean, prior_question_elapsed_time]user_df train[train.answered_correctly ! -1].groupby(user_id).agg({answered_correctly: [count, mean]}).reset_index() user_df.columns [user_id, …

chatgpt赋能Python-python3怎么安装util

如何安装python3的util模块 Python是一种高级编程语言&#xff0c;可用于从网页应用到人工智能的各种应用程序。它具有简单易学的语法和强大的功能&#xff0c;而且其可扩展性也非常好。 然而&#xff0c;要使用Python的所有功能&#xff0c;需要具有各种库和模块的支持。本文…

【设计模式】单例模式(创建型)

一、前言 学习设计模式我们关注的是什么&#xff0c;如何实现么&#xff1f;是也不是。我认为比了解如何实现设计模式更重要的是这些设计模式的应用场景&#xff0c;什么场景下我们该用这种设计模式&#xff1b;以及这些设计模式所包含的思想&#xff0c;最终帮助我们把代码写…

DNS正反向解析- 的基本实现步骤和报错解决过程

先copy一张图提升一下理解 DNS解析过程如图分为以下三种过程&#xff1a;首先是DNS客户机解析器&#xff0c;从WEB浏览器中输入相应URL&#xff0c;DNS客户机解析器会首先检查自己本地host文件是否存在网站映射关系&#xff0c;成功则直接完成域名解析。如果host中没有相关映射…

Java学习路线(3)——基础数据类型操作

一、自动类型转换 1、什么是自动类型转换&#xff1f; 自动类型转化是范围小的数据可赋值给范围大的数据且精度不损失&#xff0c;且可以跨越式转化。 基础数据类型之间的转换如下&#xff1a; byte(1)—short(2)—char(2)—int(4)—long(8)—float(4)—double(8) 2、表达式的…

cs109-energy+哈佛大学能源探索项目 Part-2.1(Data Wrangling)

博主前期相关的博客见下&#xff1a; cs109-energy哈佛大学能源探索项目 Part-1&#xff08;项目背景&#xff09; 这次主要讲数据的整理。 Data Wrangling 数据整理 在哈佛的一些大型建筑中&#xff0c;有三种类型的能源消耗&#xff0c;电力&#xff0c;冷冻水和蒸汽。 冷冻…

求二进制位中一的个数

原题链接&#xff1a;牛客网 题目内容&#xff1a; 写一个函数返回参数二进制中 1 的个数&#xff0c;负数使用补码表示。 比如&#xff1a; 15 0000 1111 4 个 1 方法一&#xff1a; #include<stdio.h>int NumberOf1(unsigned int n) {int count 0;while (n)…

分布式事务常见解决方案

分布式事务常见解决方案 一、事务介绍 事务是一系列的动作&#xff0c;它们综合在一起才是一个完的工作单元&#xff0c;这些动作必须全部完成&#xff0c;如果有一个失败的话&#xff0c;那么事务就会回滚到最开始的状态&#xff0c;仿佛什么都没发生过一样。 1、单事务概念…

通过关键字搜索接口获取alibaba国际站商品列表

作为一名技术爱好者&#xff0c;我们总会遇到各种各样的技术问题&#xff0c;需要寻找合适的技术解决方案。而在互联网时代&#xff0c;我们可以快速通过搜索引擎获取丰富的技术资源和解决方案。然而&#xff0c;在不同的技术分享中&#xff0c;我们常常会遇到质量参差不齐的文…

eggjs

官网&#xff1a;快速入门 - Egg npm init egg --typesimple 在平时安装/下载依赖时候 控制栏出现如下报错时 npm ERR! code ENOLOCAL npm ERR! Could not install from "Files\nodejs\node_cache\_npx\13944" as it does not contain a package.json file. 解释:无…

【Linux 下】 信号量

文章目录 【Linux 下】 信号量信号量概念信号量操作初始化和销毁P&#xff08;&#xff09;操作V&#xff08;&#xff09;操作理解PV操作 基于信号量与环形队列实现的CS模型基于信号量和环形队列实现的生产者与消费者模型 【Linux 下】 信号量 信号量概念 信号量&#xff08;…

蓝莓投屏 - 超低延时投屏的投屏软件

蓝莓投屏是一个低延时投屏软件&#xff0c;支持安卓、iOS、Mac 设备与Windows系统的电脑之间互相投屏&#xff0c;包括手机/平板之间互投&#xff0c;手机投电脑&#xff0c;电脑投手机 等功能。 投屏画质达到4K高清&#xff0c;播放流畅无延迟。音视频同步&#xff0c;几乎没有…

无需OpenAI API Key,构建个人化知识库的终极指南

一、介绍 今天和大家一起学习下使用LangChain&#xff0b;LLM 来构建本地知识库。 我们先来了解几个名词。 二、什么是LLM&#xff1f; LLM指的是大语言模型&#xff08;Large Language Models&#xff09;&#xff0c;大语言模型&#xff08;LLM&#xff09;是指使用大量文…

Excel中正则表达式函数的使用

有这样一列 上海市闵行区七宝镇中春路7001号37栋 021-54881702 嘉定区黄渡镇金易路1号 021-69580001 如何将地址和电话分开 这两个分成2列&#xff08;地址和电话分开&#xff09; 第一列 第二列 上海市闵行区七宝镇中春路7001号37栋 021-54881702 嘉定区黄渡镇金易路1号 021-6…

【中阳期货】GPT-4正在改进自己,超强进化

GPT是一种预训练语言模型&#xff0c;由OpenAI研发。如果你希望快速了解GPT&#xff0c;可以按照以下步骤进行&#xff1a; 了解预训练语言模型&#xff1a;预训练语言模型是一种人工智能技术&#xff0c;可以通过大量语言数据的训练&#xff0c;自动学习语言的规律和语义。GPT…

web缓存Squid代理服务

缓存网页对象&#xff0c;减少重复请求 squid代理服务器&#xff0c;主要提供缓存加速&#xff0c;应用层过滤控制的功能 代理工作机制 1.代替客户机向网站请求数据&#xff0c;从而可以隐藏用户的真实ip地址 2.将获得的网页数据&#xff08;静态web元素&#xff09;保存到缓…

Rocky Linux 8.5 安装

Rocky Linux 是一个开源的企业级操作系统&#xff0c;旨在与 Red Hat Enterprise Linux 100% 1:1 兼容。 Rocky Linux 项目是什么? 下载地址 Rocky Linux 是一个社区化的企业级操作系统。其设计为的是与美国顶级企业 Linux 发行版实现 100&#xff05; Bug 级兼容&#xff…

【学习日记2023.5.12】之 自定义封装springboot-starter案例_SpringBoot监控_Web后端开发总结

文章目录 1. 自定义封装springboot-starter案例1.1 自定义starter分析1.2 自定义starter实现1.3 自定义starter测试 2. SpringBoot优势2.1 SpringBoot监控2.1.1 Actuator2.1.2 Springboot-Admin 2.2 小结 3. Web后端开发总结 1. 自定义封装springboot-starter案例 1.1 自定义s…