【算法】单调栈问题
文章目录
- 题目
- 思路分析
- 代码实现
题目
给定一个不含有重复值的数组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,找到每一个i位置左边和右边离i位置最近且值比arr[i]小的位置,返回所有位置相应的消息。 比如arr{3,4,1,5,6,2,…...
Hack The Box - 关卡Dancing
SMB(全称是Server Message Block)是一个协议名,可用于在计算机间共享文件、打印机、串口等,电脑上的网上邻居就是靠它实现的。 SMB 是一种客户机/服务器、请求/响应协议。通过 SMB 协议,客户端应用程序可以在各种网络环境下读、写服务器上的…...
【软件设计与体系结构】 软件体系结构风格
软件体系结构(Software Architecture) 软件体系结构(Software Architecture)包括构成系统的设计元素的描述、 设计元素 之间的交互、 设计元素的组合模式以及在这些模式中的约束。 定义 软件体系结构表示系统的框架结构…...
detectron2 使用教程
本范例演示使用非常有名的目标检测框架detectron2 🤗🤗 在自己的数据集(balloon数据)上训练实例分割模型MaskRCNN的方法。 detectron2框架的设计有以下一些优点: 1,强大:提供了包括目标检测、实例分割、全景分割等非常广泛的视觉任务模型库。 2,灵活:可以通过注册机…...
哈希表常用数据结构
哈希表常用数据结构 查询一个元素是否出现过,或者一个元素是否在集合里的时候,就要第一时间想到哈希法。 哈希法也是空间换时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找。 集合底层实现…...
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. 修改内核参数,防止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存储消息时,需要设置SECRET_KEY flash 内部消息存储依赖了session 2、CSRF(Cross Site Request Forgery) 跨站请求伪造,指攻击者盗用你的身份发送恶意请求 CSRFProt…...
R语言生物群落(生态)数据统计分析与绘图(从数据整理到分析结果展示)
R 语言作的开源、自由、免费等特点使其广泛应用于生物群落数据统计分析。生物群落数据多样而复杂,涉及众多统计分析方法。以生物群落数据分析中的最常用的统计方法回归和混合效应模型、多元统计分析技术及结构方程等数量分析方法为主线,通过多个来自经典…...
代码随想录训练营Day58| 739. 每日温度 496.下一个更大元素 I
目录 学习目标 学习内容 739. 每日温度 496.下一个更大元素 I 学习目标 739. 每日温度 496.下一个更大元素 I 学习内容 739. 每日温度 739. 每日温度 - 力扣(LeetCode)https://leetcode.cn/problems/daily-temperatures/ class Solution:def da…...
设计模式-命令模式
命令模式 问题背景命令模式基本介绍UML类图 解决方案UML类图代码示例 问题背景 1)随着现在科技越来越先进,我们在家庭中对物品的开关都不需要亲自走过去来进行了。我们只需要通过手机APP中的按键来远程执行这个命令。 2)其实这就是命令模式&…...
软考——下午题部分,例题一,二,三,六
例题一 11年上半年 病人,护理人员,医生 D 生命体征范围文件 日志文件 病历文件 治疗意见文件 14年上 E1 巴士司机,2 机械师,3 会计,4 主管,5 库存管理系统 D 巴士列表文件 维修记录文件 部件清单 人事档案 14年下 1 客户 2 供应商 D 销售订单表 库存…...
关于render: h => h(App)的解释
当我们第一次安装完脚手架,打开 的时候,我相信,一定有小伙伴和我一样,看到main.js里面的render: h > h(App),感觉懵懵的。 因为,在刚开始接触vue的时候,我们这里是这样写的: 而使用了脚手…...
flask实现简易图书管理系统
项目结构 技术选型 flask 做后端, 提供数据和渲染html 暂时没有提供mysql, 后续会更新操作mysql和样式美化的版本 起一个flask服务 flask是python的一个web框架, 下面演示如何提供http接口, 并返回json数据 main.py # flask创建http接口 from flask import Flask, request, jso…...
2021 年全国大学生物联网设计竞赛(华为杯)全国总决赛获奖名单
由全国高等学校计算机教育研究会主办,上海交通大学承办,华为技术有限 公司协办,中国电信天翼物联、中国移动中移物联网、霍尼韦尔 Tridium、CSA 联盟、新大陆、德州仪器 (TI)、百度、机械工业出版社华章公司联合支持的 2021 全国大学生物联网…...
操作系统复习2.3.5-管程
引入管程 PV操作困难,容易书写出错,引入管程,作为一种高级同步机制 组成 局限于管程的共享数据结构说明对该数据结构进行操作的一组过程对局部于管程的共享数据结构设置初始值的语句管程有一个名字 基本特征 局限于管程的数据只能被局限…...
List Set Map Queue Deque 之间的区别是什么?
List Set Map Queue Deque 之间的区别是什么? 1. Java 集合框架有那些接口?2. List Set Map Queue Deque 之间的区别是什么? 1. Java 集合框架有那些接口? List、Set、Map、Queue、Deque 2. List Set Map Queue Deque 之间的区别…...
unity行为决策树实战详解
一、行为决策树的概念 行为决策树是一种用于游戏AI的决策模型,它将游戏AI的行为分解为一系列的决策节点,并通过节点之间的连接关系来描述游戏AI的行为逻辑。在行为决策树中,每个节点都代表一个行为或决策,例如移动、攻击、逃跑等…...
Spring学习记录
目录 bean的单例与多例 设置 工厂模式的三种形态 简单工厂模式 代码: 运行结果: 总结: 工厂模式 代码: 运行结果: 总结: 抽象工厂模式 代码: 运行结果: 总结: …...
用51单片机+Proteus仿真,从零到一复刻一个数码管电子钟(附完整代码和电路图)
从零构建51单片机数码管电子钟:Proteus仿真与实战全解析 数码管电子钟作为单片机入门经典项目,能系统训练定时器、中断、数码管驱动等核心技能。但很多初学者在独立实现时,常遇到仿真效果不稳定、显示闪烁或计时不准等问题。本文将用保姆级教…...
为什么选择Zabbix而不是Prometheus?K8s监控工具深度对比与实战配置
Zabbix与Prometheus在Kubernetes监控中的技术决策指南 当企业级容器平台需要构建监控体系时,技术选型往往成为困扰架构师的核心难题。作为当下最主流的两个开源监控解决方案,Zabbix与Prometheus在Kubernetes生态中的表现各有千秋。本文将基于实际生产环境…...
Catalyst API 认证管理:处理 OAuth Token 失效问题
在使用 Catalyst API 进行数据操作时,OAuth Token 的管理是至关重要的。特别是当你尝试插入新记录到 Catalyst Datastore 表时,可能会遇到 “INVALID OAUTH TOKEN” 错误。本文将详细介绍如何有效地处理这一问题,并提供一个实际的示例来演示解决方案。 问题描述 在尝试使用…...
DeTikZify:AI驱动的科研图表代码自动化解决方案
DeTikZify:AI驱动的科研图表代码自动化解决方案 【免费下载链接】DeTikZify Synthesizing Graphics Programs for Scientific Figures and Sketches with TikZ 项目地址: https://gitcode.com/gh_mirrors/de/DeTikZify 一、科研绘图的隐形痛点:我…...
Wan2.2-I2V-A14B企业应用:法律文书解读AI动画视频生成系统
Wan2.2-I2V-A14B企业应用:法律文书解读AI动画视频生成系统 1. 系统概述与核心价值 法律行业每天需要处理大量文书材料,传统的人工解读和可视化呈现方式效率低下且成本高昂。Wan2.2-I2V-A14B法律文书解读AI动画视频生成系统正是为解决这一痛点而生。 这…...
【JDK21虚拟线程生产就绪 checklist】:8类典型场景配置模板(WebFlux/Quarkus/Vert.x/RSocket全覆盖)
第一章:JDK21虚拟线程核心机制与生产就绪定义虚拟线程(Virtual Threads)是 JDK 21 中正式引入的里程碑特性(JEP 444),其本质是轻量级、用户态调度的 Java 线程抽象,由 JVM 在平台线程࿰…...
南北阁模型新玩法:一键部署极简WebUI,体验手机短信般AI对话
南北阁模型新玩法:一键部署极简WebUI,体验手机短信般AI对话 还在用那些界面老旧、反应迟钝的AI对话工具吗?每次发送问题后,只能盯着屏幕上的加载图标干等,几秒甚至十几秒后才能看到一大段文字“啪”地一下弹出来&…...
ANIMATEDIFF PRO教学创新:Jupyter Notebook交互式教程
ANIMATEDIFF PRO教学创新:Jupyter Notebook交互式教程 让AI动画学习变得像玩游戏一样有趣,实时调整参数,即刻看到效果变化 1. 引言:为什么需要交互式动画教学? 传统的AI动画教学有个痛点:学生写了一大段代…...
3D打印雕塑与玻璃钢雕塑的区别、工艺详解及定制雕塑相关疑问解答
3D打印雕塑与玻璃钢雕塑的区别、工艺详解及定制雕塑相关疑问解答3D打印雕塑与玻璃钢雕塑是当代主流雕塑工艺,核心差异在于成型逻辑与材料特性:3D打印以数字化建模为核心,遵循“分层叠加”的增材逻辑;玻璃钢以复合材料为基础&#…...
提示工程代码审查避坑指南:10个容易犯的低级错误
提示工程代码审查避坑指南:10个容易犯的低级错误 引言:为什么提示工程需要“代码审查”? 在AI时代,提示词(Prompt)是人类与大语言模型(LLM)沟通的“桥梁”。就像程序员写代码需要评审…...
