当前位置: 首页 > news >正文

【算法】单调栈问题

文章目录

  • 题目
  • 思路分析
  • 代码实现

题目

给定一个不含有重复值的数组arr,找到每一个i位置左边和右边离i位置最近且值比arr[i]小的位置,返回所有位置相应的消息。
比如arr={3,4,1,5,6,2,7},返回如下二位数组作为结果:{[-1, 2], [0, 2], [-1, -1], [2, 5], [3, 5], [2, -1], [5, -1]}
-1表示不存在,比如arr中,0位置的左边没有元素,所以是-1.右边最小的是1这个数据位置,也就是index=2,所以得到{-1,2}。

如果我给定是一个可能含有重复值的数组arr呢?

要求时间复杂度为O(N)。

思路分析

如果是时间复杂度为O(N2)的,那么我们直接暴力解决即可,都是如果这样子做,这道题肯定就g了。

我们先来分析无重复的数组的情况。

原问题:
准备一个栈,记为 Stack<Integer>,栈中放的元素是数组的位置,开始时stack 为空。如果找到每一个i位置左边和右边离i位置最近且值比 arrli]小的位置,那么需要让stack 从栈顶到栈底的位置所代表的值是严格递减的;如果找到每一个i位置左边和右边离i位置最近且值比 arr[i]大的位置,那么需要让 stack 从栈顶到栈底的位置所代表的值是严格递增的。
本题需要解决的是前者,但是对于后者,原理完全是一样的。

下面用例子来展示单调栈的使用和求解流程,初始时 arr = {3,4,1,5,6,2,7},stack 从栈顶到栈底为:{}

遍历到arr[0]==3,发现stack为空,就直接放入0位置。stack 从栈顶到栈底为:{0位置(值是3));

遍历到arr[1]==4,发现直接放入1位置,不会破坏stack 从栈顶到栈底的位置所代表的值是严格递减的,那么直接放入。stack从栈顶到栈底依次为:(1位置(值是4)、0位置(值是3);

遍历到arr[2]==1,发现直接放入2位置(值是1),会破坏stack 从栈顶到栈底的位置所代表的值是严格递减的,所以从 stack开始弹出位置。如果x位置被弹出,在栈中位于x位置下面的位置,就是x位置左边离x位置最近且值比 arr[x]小的位置;
当前遍历到的位置就是x位置右边离x位置最近且值比 arr[x]小的位置。
从 stack弹出位置1,在栈中位于1位置下面的是位置0,当前遍历到的是位置2,所以 ans[1]=(0.2}。
弹出1位置之后,发现放入2位置(值是1)还会破坏stack 从栈顶到栈底的位置所代表的值是严格递减的,所以继续弹出位置0。
在栈中位于位置0下面已经没有位置了,说明在位置О左边不存在比 arr[0]小的值,当前遍历到的是位置2,所以ans[0]=(-1,2}。stack 已经为空,所以放入2位置(值是1),stack从栈顶到栈底为:{2位置(值是1));

遍历到 arr[3]==5,发现直接放入3位置,不会破坏stack 从栈顶到栈底的位置所代表的值是严格递减的,那么直接放入。stack 从栈顶到栈底依次为:3位置(值是5)、2位置(值是1);

遍历到 arr[4]==6,发现直接放入4位置,不会破坏 stack 从栈顶到栈底的位置所代表的值是严格递减的,那么直接放入。stack从
栈顶到栈底依次为:{(4位置(值是6)、3位置(值是5)、2位置(值是1);

遍历到 arr[5]==2,发现直接放入5位置,会破坏stack从栈顶到栈底的位置所代表的值是严格递减的,所以开始弹出位置。弹出位置4,栈中它的下面是位置3,当前是位置5, ans[4]=(3,5}。弹出位置3,栈中它的下面是位置2,当前是位置5,ans[3]=(2,5}。然后放入5位置就不会破坏stack的单调性了。stack从栈顶到栈底依次为:{5位置(值是2)、2位置(值是1)};

遍历到arr[6]==7,发现直接放入6位置,不会破坏stack从栈顶到栈底的位置所代表的值是严格递减的,那么直接放入。stack从栈顶到栈底依次为:{6位置(值是7)、5位置(值是2)、2位置(值是1)}。

遍历阶段结束后,清算栈中剩下的位置。

弹出6位置,栈中它的下面是位置5,6位置是清算阶段弹出的,所以 ans[6]={5,-1];弹出5位置,栈中它的下面是位置2,5位置是清算阶段弹出的,所以 ans[5]={2,-1];弹出2位置,栈中它的下面没有位置了,2位置是清算阶段弹出的,所以 ans[2]=(-1,-1]。

至此,已经全部生成了每个位置的信息。

我们可以按照上面的流程写出如下的代码

  public static int[][] monotonicStackNorepeat(int[] arr) {Stack<Integer> stack = new Stack<>();int[][] res = new int[arr.length][2];for (int i = 0; i < arr.length; i++) {//如果当前栈不为空并且当前值比栈顶对应的元素小//那么就开始出栈 因为这说明栈内元素遇到了比自己小的数据了//并且一直出栈直到栈顶元素比当前元素小while (!stack.isEmpty() && arr[stack.peek()] > arr[i]) {//出栈得到栈顶元素对应的索引int popIndex = stack.pop();//判断栈顶元素的左边是否还有元素 如果有 那么比栈顶元素的左边最小就是这个元素int leftLessIndex = stack.isEmpty() ? -1 : stack.peek();res[popIndex][0] = leftLessIndex;//比栈顶元素右边小的元素的位置为ires[popIndex][1] = i;}//放入当前元素 开启新一轮循环stack.push(i);}//清算阶段 对于还在栈中的元素while (!stack.isEmpty()) {//取出当前元素对应的索引位置int popIndex = stack.pop();//判断是否他们的左边还有值?左边的值都是比他们小的值int leftLessIndex = stack.isEmpty() ? -1 : stack.peek();res[popIndex][0] = leftLessIndex;//清算阶段还在栈中说明他们的右边都是比他们大的或者就是已经没有后面的元素了res[popIndex][1] = -1;}return res;}

对于进阶问题,她的情况是,可能出现重复的数据,但是大体的解答流程差不多,思路如下:

进阶问题,可能含有重复值的数组如何使用单调栈。其实整个过程和原问题的解法差不多。举个例子来说明,初始时 arr={3,1,3,4,3,5,3,2,2],stack从栈顶到栈底为:{};

遍历到 arr[0]==3,发现stack为空,就直接放入0位置。stack 从栈顶到栈底为:{0位置(值是3)};

遍历到arr[1]==1,从栈中弹出位置0,并且得到ans[0]=[-1,1}。位置1进栈,stack从栈顶到栈底为:{1位置(值是1)};

遍历到arr[2]==3,发现位置2可以直接放入。stack从栈顶到栈底依次为:{2位置(值是3).1位置(值是1)};

遍历到 arr[3]==4,发现位置3可以直接放入。stack从栈顶到栈底依次为:{3位置(值是4)、2位置(值是3)、1位置(值是1)};

遍历到arr[4]==3,从栈中弹出位置3,并且得到ans[3]={2,4}。此时发现栈顶是位置2,值是3,当前遍历到位置4,值也是3,所以两个位置压在一起。stack 从栈顶到栈底依次为:{2位置,4位置、1位置(值是1)};

遍历到arr[5]==5,发现位置5可以直接放入。stack 从栈顶到栈底依次为:{5位置(值是5)、2位置,4位置、1位置(值是1));

遍历到arr[6]==3,从栈中弹出位置5,在栈中位置5的下面是[2位置,4位置],选最晚加入的4位置,当前遍历到位置6,所以得到 ans[5]={4,6}。位置6进栈,发现又是和栈顶位置代表的值相等的情况,所以继续压在一起,stack 从栈顶到栈底依次为:{2位置,4位置,6位置、1位置(值是1)};

遍历到arr[7]==2,从栈中弹出[2位置,4位置,6位置],在栈中这些位置下面的是1位置,当前是7位置,所以得到ans[2]=(1,7]、ans[4]=(1,7]、ans[6]={1,7}]。位置7进栈,stack 从栈顶到栈底依次为:{7位置(值是2)、1位置(值是1)};

遍历到arr[8]==2,发现位置8可以直接进栈,并且又是相等的情况,stack从栈顶到栈底依次为:{7位置,8位置、1位置(值是1)}。

遍历完成后,开始清算阶段:

弹出[7位置,8位置],生成ans[7]={1,-1]、ans[8]={1,-1};弹出1位置,生成ans[1]={-1,-1}。

完整代码贴在下面:

代码实现

package com.base.learn.stack;import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;/*** @author: 张锦标* @date: 2023/5/28 11:00* MonotonicStack类* 单调栈题目*/
public class MonotonicStack {public static int[][] violentSolution(int[] arr) {int[][] res = new int[arr.length][2];for (int i = 0; i < arr.length; i++) {int leftMin = -1;int rightMin = -1;int cur = i - 1;while (cur >= 0) {if (arr[cur] < arr[i]) {leftMin = cur;break;}cur--;}cur = i + 1;while (cur < arr.length) {if (arr[cur] < arr[i]) {rightMin = cur;break;}cur++;}res[i][0] = leftMin;res[i][1] = rightMin;}return res;}public static int[][] monotonicStackNorepeat(int[] arr) {Stack<Integer> stack = new Stack<>();int[][] res = new int[arr.length][2];for (int i = 0; i < arr.length; i++) {//如果当前栈不为空并且当前值比栈顶对应的元素小//那么就开始出栈 因为这说明栈内元素遇到了比自己小的数据了//并且一直出栈直到栈顶元素比当前元素小while (!stack.isEmpty() && arr[stack.peek()] > arr[i]) {//出栈得到栈顶元素对应的索引int popIndex = stack.pop();//判断栈顶元素的左边是否还有元素 如果有 那么比栈顶元素的左边最小就是这个元素int leftLessIndex = stack.isEmpty() ? -1 : stack.peek();res[popIndex][0] = leftLessIndex;//比栈顶元素右边小的元素的位置为ires[popIndex][1] = i;}//放入当前元素 开启新一轮循环stack.push(i);}//清算阶段 对于还在栈中的元素while (!stack.isEmpty()) {//取出当前元素对应的索引位置int popIndex = stack.pop();//判断是否他们的左边还有值?左边的值都是比他们小的值int leftLessIndex = stack.isEmpty() ? -1 : stack.peek();res[popIndex][0] = leftLessIndex;//清算阶段还在栈中说明他们的右边都是比他们大的或者就是已经没有后面的元素了res[popIndex][1] = -1;}return res;}public static int[][] monotonicStackRepeat(int[] arr) {Stack<List<Integer>> stack = new Stack<>();int[][] res = new int[arr.length][2];for (int i = 0; i < arr.length; i++) {//如果当前栈不为空并且当前值比栈顶对应的元素小//那么就开始出栈 因为这说明栈内元素遇到了比自己小的数据了//并且一直出栈直到栈顶元素比当前元素小while (!stack.isEmpty() && arr[stack.peek().get(0)] > arr[i]) {//出栈得到栈顶元素对应的索引List<Integer> popList = stack.pop();//判断栈顶元素的左边是否还有元素 如果有 那么比栈顶元素的左边最小就是这个元素int leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size()-1);for (Integer popi : popList) {res[popi][0]=leftLessIndex;res[popi][1] = i;}}//判断当前栈是否为空 不为空则取出栈顶列表并且放入当前元素if (!stack.isEmpty() && arr[stack.peek().get(0)]==arr[i]){stack.peek().add(Integer.valueOf(i));}else{//栈为空 或者当前元素与栈顶元素不一样 那么直接创建一个新的listArrayList<Integer> list = new ArrayList<>();list.add(i);stack.push(list);}}//清算阶段 对于还在栈中的元素while (!stack.isEmpty()) {//出栈得到栈顶元素对应的索引List<Integer> popList = stack.pop();//判断栈顶元素的左边是否还有元素 如果有 那么比栈顶元素的左边最小就是这个元素int leftLessIndex = stack.isEmpty() ? -1 : stack.peek().get(stack.peek().size()-1);for (Integer popi : popList) {res[popi][0]=leftLessIndex;res[popi][1] = -1;}}return res;}public static void main(String[] args) {System.out.println(Arrays.deepToString(monotonicStackNorepeat(new int[]{3,4,1,5,6,2,7})));}
}

相关文章:

【算法】单调栈问题

文章目录 题目思路分析代码实现 题目 给定一个不含有重复值的数组arr&#xff0c;找到每一个i位置左边和右边离i位置最近且值比arr[i]小的位置&#xff0c;返回所有位置相应的消息。 比如arr{3&#xff0c;4&#xff0c;1&#xff0c;5&#xff0c;6&#xff0c;2&#xff0c;…...

Hack The Box - 关卡Dancing

SMB(全称是Server Message Block)是一个协议名&#xff0c;可用于在计算机间共享文件、打印机、串口等&#xff0c;电脑上的网上邻居就是靠它实现的。 SMB 是一种客户机/服务器、请求/响应协议。通过 SMB 协议&#xff0c;客户端应用程序可以在各种网络环境下读、写服务器上的…...

【软件设计与体系结构】 软件体系结构风格

软件体系结构&#xff08;Software Architecture&#xff09; 软件体系结构&#xff08;Software Architecture&#xff09;包括构成系统的设计元素的描述、 设计元素 之间的交互、 设计元素的组合模式以及在这些模式中的约束。 定义 软件体系结构表示系统的框架结构&#xf…...

detectron2 使用教程

本范例演示使用非常有名的目标检测框架detectron2 🤗🤗 在自己的数据集(balloon数据)上训练实例分割模型MaskRCNN的方法。 detectron2框架的设计有以下一些优点: 1,强大:提供了包括目标检测、实例分割、全景分割等非常广泛的视觉任务模型库。 2,灵活:可以通过注册机…...

哈希表常用数据结构

哈希表常用数据结构 查询一个元素是否出现过&#xff0c;或者一个元素是否在集合里的时候&#xff0c;就要第一时间想到哈希法。 哈希法也是空间换时间&#xff0c;因为我们要使用额外的数组&#xff0c;set或者是map来存放数据&#xff0c;才能实现快速的查找。 集合底层实现…...

Java字节流

4 字节流 字节流抽象基类 InputStream:这个抽象类是表示字节输入流的所有类的超类OutputStream:这个抽象类是表示字节输出流的所有类的超类子类名特点:子类名称都是以其父类名作为子类名的后缀4.1 IO流概述和分类 IO流概述: IO: 输入/输出(Input/Output)流:是一种抽象概念…...

arm3399主板-使用ubuntu20.04搭建LVS-DR(netplan)

目录 一、规划 1、网络拓扑 2、检查 二、配置设备 1、配置LVS 1.配置IP转发 2.清除防火墙 3.安装ipvsadm工具 4.配置VIP 5.netplan与NetworkManager介绍 6.添加LVS规则 1.清除防火墙 2.添加伪装IP 3.安装web服务 4. 修改内核参数&#xff0c;防止IP冲突 3、配置w…...

Go中同/异步与锁的应用~~sync包

Go中锁的实现~~sync包 go中sync包中提供了互斥锁; 在前面Go中channel文章中我们使用了time.Sleep()函数使得main函数的Goroutine阻塞至所有协程Goroutine结束,但这并不是一个很好的办法,因为我们实际应用中并不能准确知道协程什么时候结束(这里面要考虑服务器的性能,网络波动以…...

Flask知识点2

1、flash() get_flashed_messages() : 用来消耗flash方法中存储的消息 使用flash存储消息时&#xff0c;需要设置SECRET_KEY flash 内部消息存储依赖了session 2、CSRF(Cross Site Request Forgery) 跨站请求伪造&#xff0c;指攻击者盗用你的身份发送恶意请求 CSRFProt…...

R语言生物群落(生态)数据统计分析与绘图(从数据整理到分析结果展示)

R 语言作的开源、自由、免费等特点使其广泛应用于生物群落数据统计分析。生物群落数据多样而复杂&#xff0c;涉及众多统计分析方法。以生物群落数据分析中的最常用的统计方法回归和混合效应模型、多元统计分析技术及结构方程等数量分析方法为主线&#xff0c;通过多个来自经典…...

代码随想录训练营Day58| 739. 每日温度 496.下一个更大元素 I

目录 学习目标 学习内容 739. 每日温度 496.下一个更大元素 I 学习目标 739. 每日温度 496.下一个更大元素 I 学习内容 739. 每日温度 739. 每日温度 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/daily-temperatures/ class Solution:def da…...

设计模式-命令模式

命令模式 问题背景命令模式基本介绍UML类图 解决方案UML类图代码示例 问题背景 1&#xff09;随着现在科技越来越先进&#xff0c;我们在家庭中对物品的开关都不需要亲自走过去来进行了。我们只需要通过手机APP中的按键来远程执行这个命令。 2&#xff09;其实这就是命令模式&…...

软考——下午题部分,例题一,二,三,六

例题一 11年上半年 病人&#xff0c;护理人员&#xff0c;医生 D 生命体征范围文件 日志文件 病历文件 治疗意见文件 14年上 E1 巴士司机,2 机械师,3 会计,4 主管,5 库存管理系统 D 巴士列表文件 维修记录文件 部件清单 人事档案 14年下 1 客户 2 供应商 D 销售订单表 库存…...

关于render: h => h(App)的解释

当我们第一次安装完脚手架&#xff0c;打开 的时候&#xff0c;我相信&#xff0c;一定有小伙伴和我一样&#xff0c;看到main.js里面的render: h > h(App),感觉懵懵的。 因为&#xff0c;在刚开始接触vue的时候&#xff0c;我们这里是这样写的&#xff1a; 而使用了脚手…...

flask实现简易图书管理系统

项目结构 技术选型 flask 做后端, 提供数据和渲染html 暂时没有提供mysql, 后续会更新操作mysql和样式美化的版本 起一个flask服务 flask是python的一个web框架, 下面演示如何提供http接口, 并返回json数据 main.py # flask创建http接口 from flask import Flask, request, jso…...

2021 年全国大学生物联网设计竞赛(华为杯)全国总决赛获奖名单

由全国高等学校计算机教育研究会主办&#xff0c;上海交通大学承办&#xff0c;华为技术有限 公司协办&#xff0c;中国电信天翼物联、中国移动中移物联网、霍尼韦尔 Tridium、CSA 联盟、新大陆、德州仪器 (TI)、百度、机械工业出版社华章公司联合支持的 2021 全国大学生物联网…...

操作系统复习2.3.5-管程

引入管程 PV操作困难&#xff0c;容易书写出错&#xff0c;引入管程&#xff0c;作为一种高级同步机制 组成 局限于管程的共享数据结构说明对该数据结构进行操作的一组过程对局部于管程的共享数据结构设置初始值的语句管程有一个名字 基本特征 局限于管程的数据只能被局限…...

List Set Map Queue Deque 之间的区别是什么?

List Set Map Queue Deque 之间的区别是什么&#xff1f; 1. Java 集合框架有那些接口&#xff1f;2. List Set Map Queue Deque 之间的区别是什么&#xff1f; 1. Java 集合框架有那些接口&#xff1f; List、Set、Map、Queue、Deque 2. List Set Map Queue Deque 之间的区别…...

unity行为决策树实战详解

一、行为决策树的概念 行为决策树是一种用于游戏AI的决策模型&#xff0c;它将游戏AI的行为分解为一系列的决策节点&#xff0c;并通过节点之间的连接关系来描述游戏AI的行为逻辑。在行为决策树中&#xff0c;每个节点都代表一个行为或决策&#xff0c;例如移动、攻击、逃跑等…...

Spring学习记录

目录 bean的单例与多例 设置 工厂模式的三种形态 简单工厂模式 代码&#xff1a; 运行结果&#xff1a; 总结&#xff1a; 工厂模式 代码&#xff1a; 运行结果&#xff1a; 总结&#xff1a; 抽象工厂模式 代码&#xff1a; 运行结果&#xff1a; 总结&#xff1a; …...

模板方法-

定义&#xff1a;又叫模板模式,是指定义一个算法骨架,并允许子类为其中的一个或多个步骤提供实现。 适用场景&#xff1a; 1、一次性实现一个算法不变的部分,并将可变的行为留给子类来实现 2、各子类中公共的行为被提取出来并集中到一个公共的父类中,从而避免代码重复 优点…...

[Kubernetes] - RabbitMQ学习

1.消息队列 消息&#xff1a; 在应用间传送的数据队列&#xff0c;先进先出 1.2. 作用 好处&#xff1a;解耦&#xff0c; 容错&#xff0c;削峰坏处&#xff1a;降低系统可用性&#xff0c;系统复杂度提高&#xff0c;一致性问题&#xff1b; RabbitMQ组成部分&#xff1a…...

swagger页面 doc.html出不来,swagger-ui/index.html能出来

swagger页面 doc.html出不来&#xff0c;swagger-ui/index.html能出来。前前后后折腾了很久&#xff0c;jar包冲突&#xff0c;jar包版本&#xff0c;添加路径啥的都弄了&#xff0c;就是出不来。 后来全局搜索“doc.html”页面发现能出来的项目能搜到这个页面&#xff1a; 定…...

IEEE802.3和IEEE802.11的分类(仅为分类)

IEEE802.3标准 IEEE802.3:10兆以太网 ●10Base&#xff0d;5 使用粗同轴电缆&#xff0c;最大网段长度为500m&#xff0c;基带传输方法&#xff1b; ●10Base&#xff0d;2 使用细同轴电缆&#xff0c;最大网段长度为185m&#xff0c;基带传输方法&#xff1b; ●10Base&am…...

c# cad二次开发通过获取excel数据 在CAD绘图,将CAD属性导出到excel

c# cad二次开发通过获取excel数据 在CAD绘图&#xff0c;将CAD属性导出到excel using Autodesk.AutoCAD.ApplicationServices; using Autodesk.AutoCAD.EditorInput; using Autodesk.AutoCAD.Runtime; using System; using System.Collections.Generic; using System.Linq; us…...

LLM之高性能向量检索库

LLM向量数据库 高性能向量检索库milvus简介安装调用 faiss简介安装调用 高性能向量检索库 milvus 简介 Milvus 是一个开源的向量数据库引擎&#xff0c;旨在提供高效的向量存储、检索和分析能力。它被设计用于处理大规模的高维向量数据&#xff0c;常用于机器学习、计算机视觉…...

实体类注解

目录 一、TableField注解 二、TableId注解 三、Table注解 四、TableLogic注解 五、Getter与Setter注解 六、EqualsAndHashCode注解 七、Accessors注解 一、TableField注解 Data NoArgsConstructor //空参构造方法 AllArgsConstructor //全参构造方法 TableName("t…...

常见数据结构种类

常见数据结构种类 数据存储的常用结构有&#xff1a;栈、队列、数组、链表和红黑树 a.队列&#xff08;queue&#xff09; – 先进先出&#xff0c;后进后出。 – 场景&#xff1a;各种排队。叫号系统。 – 有很多集合可以实现队列。 b.栈&#xff08;stack&#xff09; – …...

linux高级---k8s中的五种控制器

文章目录 一、k8s的控制器类型二、pod与控制器之间的关系三、状态与无状态化对特点四、Deployment1、Deployment的资源清单文件2、在配置清单中调用deployment控制器3、镜像更新4、金丝雀发布5、删除Deployment 五、Statefulset六、DaemonSet1、daemonset的资源清单文件2、在配…...

记一次udp服务性能优化经历

目录 概述磁盘io网络io减少重复计算减少内存复制减少互斥锁 概述 手上有个go项目&#xff0c;接收udp信息&#xff08;主要是syslog和snmp trap&#xff09;并查询设备信息&#xff0c;将信息结构化&#xff08;设备ip名称&#xff0c;匹配了什么规则之类的&#xff09;后发送…...