单调栈总结以及Leetcode案例解读与复盘

单调栈总结以及Leetcode案例解读与复盘

一、单调栈是什么?

单调栈(monotonous stack)是指栈的内部从栈底到栈顶满足单调性的栈结构。

二、如何维护单调性

新元素入栈时,会与栈顶元素进行比较,使得栈始终保持单调性。

for example,如果需要构造一个从栈底到栈顶单调递减的栈

  • 如果要插入的元素x是小于栈顶元素的,直接插入即可。
  • 如果要插入的元素x是大于栈顶元素的,说明需要弹出部分元素直到x是小于栈顶元素的,以确保插入该元素x后栈的单调性
while(stack1.length && nums[i] > stack1.at(-1)){
		// operations #1
     stack1.pop();
}
	 // operations #2**
stack1.push(nums[i]);

上面的代码里有两处可以增加附加操作(即operations #1和operations #2)

三、维护单调性过程中元素与栈顶元素之间的关系

下面以创建一个单调递减栈为例讲解一下。

  1. 被遍历到的元素入栈时:
    在这里插入图片描述

    如上图所示,

    添加元素1入栈时,由于元素1能直接入栈且不破坏单调性,因此可以直接push进入。

    在1入栈前,我们可以观察到,栈中最小的元素即为栈顶的3,因此即将入栈的1在原数组位置的左侧比自己大的元素只能是 3。

    ⇒ 如果一个元素入栈后不破坏单调栈的单调性,那么栈顶元素就是待入栈元素在原数组位置左侧第一个比自己大的元素。

    如果要求一个元素左边第一个比自己大的元素,就从左向右遍历,构建单调递减栈。更新待入栈的元素的左侧第一个比自己大的元素是栈顶元素。

    如果要求一个元素左边第一个比自己小的元素,就从左向右遍历,构建单调递增栈。更新待入栈的元素的左侧第一个比自己小的元素是栈顶元素。

  2. 当栈内元素出栈时:

在这里插入图片描述

如上图,

当元素5要入栈时,发现入栈后不能满足单调性。栈顶的1比5小,因此必须pop出栈顶元素1。

这里即将被pop出的1和待入栈的5也蕴含一种关系:5是打破1所在位置单调性的原因,假如5和1之间还有个元素0.2,那么0.2 会被压入栈,并不会打破栈的单调性,因此待入栈的5是即将出栈的1右侧第一个比自己大的元素。

⇒ 由于一个待入栈元素要入栈,导致一个元素出栈时,这个待入栈元素是该出栈元素右侧第一个比自己大的元素。

要求一个元素右边第一个比自己大的数,就从左向右遍历,构造单调递减栈,更新出栈元素的右侧最大值为待入栈元素。

要求一个元素右边第一个比自己小的数,就从左向右遍历,构造单调递增栈,更新出栈元素的右侧最大值为待入栈元素。

总结,从左向右,待入栈的元素只能找出左侧的符合某个特性的元素,右侧信息是不知道的;待出栈的元素只能知道右侧的符合某个特性的元素。

四、基础案例

问题:给定一个数组,找到每个元素的下一个更大元素。如果不存在更大的元素,则将其设为-1。

示例:

输入:[2, 5, 9, 3, 1, 12, 6, 8, 7]

输出:[5, 9, 12, 12, 12, -1, 8, -1, -1]

解释:对于输入数组中的每个元素,找到它右边第一个比它大的元素。如果不存在这样的元素,则将其设为-1。因此,对于2,右边第一个比它大的元素是5;对于5,右边第一个比它大的元素是9;对于9,右边第一个比它大的元素是12;对于3、1、6,它们右边都存在比它们大的元素,所以对应的结果分别是12、12、12;对于12、8、7,它们右边都不存在比它们大的元素,所以对应的结果分别是-1、-1、-1。

从左向右遍历构造单调递减栈,更新出栈元素的右侧最大值为待入栈元素

/**
 * @param {number[]} temperatures
 * @return {number[]}
 */
var nextGreaterElements= function (nums) {
  let stack = [];
  let res = new Array(nums.length).fill(-1);
  for(let i = 0; i < nums.length ; i++){
    while(stack.length && nums[i] > nums[stack.at(-1)]){
        res[stack.at(-1)] = nums[i];
				stack.pop()
    }
    stack.push(i);
  }
  return res;
};

// 示例用法
const nums = [2, 5, 9, 3, 1, 12, 6, 8, 7];
const result = nextGreaterElements(nums);
console.log(result); // 输出: [5, 9, 12, 12, 12, -1, 8, -1, -1]

739 每日温度

https://leetcode.cn/problems/daily-temperatures/solutions/868874/yi-pian-ti-jie-gao-ding-dan-diao-zhan-we-2pkf/

给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。

从左向右遍历构造单调递减栈,更新出栈元素的右侧最大值为待入栈元素

/**
 * @param {number[]} temperatures
 * @return {number[]}
 */
var dailyTemperatures = function (temperatures) {
  let stack = [];
  let res = new Array(temperatures.length).fill(0);
  for(let i = 0; i < temperatures.length ; i++){
    while(stack.length && temperatures[i] > temperatures[stack.at(-1)]){
        res[stack.at(-1)] = i - stack.pop();
    }
    stack.push(i);
  }
  return res;
};

496. 下一个更大元素

nums1 中数字 x下一个更大元素 是指 xnums2 中对应位置 右侧第一个x ****大的元素。

给你两个 没有重复元素 的数组 nums1nums2 ,下标从 0 开始计数,其中nums1nums2 的子集。

对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j]下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1

返回一个长度为 nums1.length 的数组 ans 作为答案,满足 ans[i] 是如上所述的 下一个更大元素

思路:

只需要计算nums2中每个元素的下一个,然后nums1根据map可以去获取,可以这么做的理由是因为nums1 的元素在nums2 里都可以找到。而且nums2 的元素是没有重复的。

⇒ 找出下一个更大的元素。
从左向右遍历构造单调递减栈,更新出栈元素的右侧最大值为待入栈元素

  var nextGreaterElement = function(nums1, nums2) {
    let res = new Array(nums1.length).fill(-1);
    let map2 = new Map();
    let stack = []
    for(let i = 0; i < nums2.length; i++){
        map2.set(nums2[i],-1)
        while(stack.length && nums2[i] > stack.at(-1)){
            map2.set(stack.pop(), nums2[i]);
        }
        stack.push(nums2[i]);
    }
    console.log(map2)
    for(let i = 0; i < nums1.length; i++){
        res[i] = map2.get(nums1[i]);
    }
    return res;
};

503. 下一个更大元素

如果提到循环首尾相连等字样,应该能立马联想到模运算(求余运算),也就是下标 % 长度,本题中就用到了模运算。

给定一个循环数组 numsnums[nums.length - 1] 的下一个元素是 nums[0] ),返回 nums 中每个元素的 下一个更大元素

数字 x下一个更大的元素 是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1

思路:

其实最简单的方式就是将数组进行翻倍处理,这里的翻倍不是扩容为两倍,而是遍历两遍就可以了,采用模运算的方式来处理,不是真正地翻倍

⇒ 找出下一个更大的元素。
从左向右遍历构造单调递减栈,更新出栈元素的右侧最大值为待入栈元素

对 [1,2,4,3]

遍历 2 遍的做法 0 1 2 3 4%4 5%4 6%4 7%4

nums.length * 2 - 1

var nextGreaterElements = function(nums) {
    let stack = [];
    let res = new Array(nums.length).fill(-1);
    for(let i = 0;i<2*nums.length; i++){
        while(stack.length && nums[i%nums.length] > nums[stack.at(-1)]){
            res[stack.at(-1)] = nums[i%nums.length];
            stack.pop();
        }
        stack.push(i%nums.length);
    }
    return res;
};

402. 移掉 K 位数字

给你一个以字符串表示的非负整数 num 和一个整数 k ,移除这个数中的 k **位数字,使得剩下的数字最小。请你以字符串形式返回这个最小的数字。

示例 1 :

输入:num = "1432219", k = 3
输出:"1219"
解释:移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219 。

如果有 m+1 位数字,S1

a 0 a 1 a 2 . . . . a m a_0a_1a_2....a_m a0a1a2....am

需要去掉n位数字,S2

a i , a k , , , , a n a_i,a_k,,,,a_n ai,ak,,,,an

剩余的 m+1-n 位。S3

假如用去掉的 n 位数字中的一个数字

a i ( a i < a j ) a_i (a_i < a_j) aiai<aj

替换剩余的 S3 里面的最大的字符 (a_j), 那么 新的数字会比原来的S3 要小,因此,我们用来去掉的n位数字中的每一个数字都需要大于剩余的数字

⇒ 去掉前N个最大的数字。

⇒ 通过单调栈去掉极大值,尖峰。

注意点:

遍历完毕后,k个数字没有移除完,比如数字123456789,移除3个数字,按照上面的分析,得出的结果还是123456789,出现这种情况是因为移除部分数字后,得出的结果是一个高位递增的数,所以无法再移除了,这个时候,只要出现这种情况,将低位的数字移除掉剩余个数即可,可以仔细想想这一个特殊点

通过上述的解释,我们需要从左向右遍历数组,然后构建单调递增栈,尽量将小的数往高位放。

var removeKdigits = function(num, k) {

    if(k === num.length){
        return "0";
    }

    let stack = [];
    for(let i = 0; i < num.length; i++){
        // 遇到更小的数,将大的数弹出,维护k的大小。
        while(k && stack.length && num[i]< stack.at(-1)  ){
            stack.pop();
            k--;
        }
        // 维护的是一个递增的栈,但是首位不能是0
        if(!(nums[i]==="0" && stack.length === 0)){
            stack.push(num[i]);
        }
    }

// 把低位的删除即可 ||单调递增
    while(k>0 && stack.length){
        stack.pop();
        k--;
    }

    return stack.length === 0 ? "0" : stack.join("");

};

581. 最短无序连续子数组

给你一个整数数组 nums ,你需要找出一个 连续子数组 ,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。

请你找出符合题意的 最短 子数组,并输出它的长度。

示例 1:

输入:nums = [2,6,4,8,10,9,15]
输出:5
解释:你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。

示例 2:

输入:nums = [1,2,3,4]
输出:0

示例 3:

输入:nums = [1]
输出:0

思路一

  • 将nums 进行排序获得新的数组sortedArr
  • 两个数组进行从左到右进行对比,找出第一位不同的元素,用 left 标志位置,再从右到左,找不同的元素,用right标志。
  • right - left + 1 就是子数组的长度。

思路二

从左向右,构造一个单调递增的栈,一旦遇到需要出栈的,说明要入栈的元素小于栈顶元素。
从右向左,构造一个单调递减的栈,一旦遇到需要出栈的,说明要入栈的元素大于栈顶元素


/**
 * @param {number[]} nums
 * @return {number}
 */
var findUnsortedSubarray = function(nums) {
    if(nums.length === 1){
        return 0;
    }

    // 从左到右构造一个单调递增的栈
    let left = nums.length - 1;
    let stack1 = [];
    for(let i = 0; i < nums.length; i++){
        while(stack1.length && nums[i] < nums[stack1.at(-1)]){
            left = Math.min(left, stack1.pop());
        }
        stack1.push(i);
    }

    // 从右到左构造一个单调递减的栈
    let right = 0;
    let stack2 = [];
    for(let i = nums.length - 1; i >= 0; i--){
        while(stack2.length && nums[i] > nums[stack2.at(-1)]){
            right = Math.max(right, stack2.pop());
        }
        stack2.push(i);
    }
    return left > right ? 0 : right - left + 1;
};

例题1

对山峰群编号1-N,观测员站在奇数编号山顶观测其左边比当前山峰高的个数,观测员站在奇数编号山顶观测其右边比当前山峰高的个数。
统计观测员观测到的山峰个数和。注:相同高度的山峰能被遮挡,只能看到最近的一座山峰。

1 <= N <= 100000
1 <= height <= 1000000

举例1
N = 6 height[6] = {16, 5, 3, 10, 21, 7} 观测到的山峰个数为8 = 0 + 3 + 2 + 1 + 2 + 0

思路:

从左到右遍历构建一个单调递减栈,一旦遇到更大的,需要将栈里小于更大元素都清掉,因为右边元素能看到的左边的山已经被更大的元素盖住了。
从右到左也构建一个单调递减栈。

const NumberOfMountainSeen = ( height) =>{
    let oddStack = []
    let oddResult = []
    for(let i = 0; i < height.length; i++){
        oddResult.push(oddStack.length ? oddStack.length : 0)
        while(oddStack.length && height[i] >= oddStack.at(-1)){
            oddStack.pop()
        }
        oddStack.push(height[i])
    }

    let evenStack = []
    let evenResult = []
    for(let i = height.length - 1; i >= 0; i--){
        evenResult.unshift(evenStack.length ? evenStack.length : 0)
        while(oddStack.length && height[i] >= evenStack.at(-1)){
            evenStack.pop()
        }
        evenStack.push(height[i])
    }
    let sum = 0;
    
    for(let i = 0; i < height.length; i++){
        if(i%2==0){
            sum+=oddResult[i]
        }else{
            sum+=evenResult[i]
        }
    }
    console.log(evenResult)
    return sum;

}
console.log(NumberOfMountainSeen([16,5,3,10,21,7]))

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Redis-内存管理

Redis是基于内存存储的&#xff0c;非关系型&#xff0c;键值对数据库。因此&#xff0c;对Redis来说&#xff0c;内存空间的管理至关重要。那Redis是如何内存管理的呢&#xff1f; 一、最大内存限制 Redis 提供了 maxmemory 参数允许用户设置 Redis 可以使用的最大内存大小。…

2023年12月CCF-GESP编程能力等级认证C++编程五级真题解析

一、单选题(每题 2 分,共 30 分) 第1题 下面C++代码用于求斐波那契数列,该数列第1、2项为1,以后各项均是前两项之和。下面有关说法错误的是( )。 A. fiboA( ) 用递归方式, fiboB() 循环方式 B. fiboA( ) 更加符合斐波那契数列的数学定义,直观易于理解,而 fiboB() 需…

Android TV遥控器探索,Android 桌面应用程序

Android TV 的遥控功能是通过红外遥控器或蓝牙遥控器来实现的。下面分别介绍这两种遥控器的工作原理&#xff1a; 红外遥控器&#xff1a; 红外遥控器是最常见的 Android TV 遥控器类型之一。 红外遥控器通过发送红外信号来控制电视或机顶盒。每个按键都有一个特定的红外编码&…

基于springboot+vue的课程答疑系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、阿里云专家博主、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战&#xff0c;欢迎高校老师\讲师\同行交流合作 ​主要内容&#xff1a;毕业设计(Javaweb项目|小程序|Pyt…

【算法 - 动态规划】最长回文子序列

上篇文章中&#xff0c;我们学习一个新的模型&#xff1a; 样本对应模型&#xff0c;该模型的套路就是&#xff1a;以结尾位置为出发点&#xff0c;思考两个样本的结尾都会产生哪些可能性 。 而前篇文章中的 纸牌博弈问题 属于 [L , R]上范围尝试模型。该模型给定一个范围&…

【洛谷 P8780】[蓝桥杯 2022 省 B] 刷题统计 题解(二分查找+数学)

[蓝桥杯 2022 省 B] 刷题统计 题目描述 小明决定从下周一开始努力刷题准备蓝桥杯竞赛。他计划周一至周五每天做 a a a 道题目&#xff0c;周六和周日每天做 b b b 道题目。请你帮小明计算&#xff0c;按照计划他将在第几天实现做题数大于等于 n n n 题? 输入格式 输入一…

Android14 InputManager-InputReader的处理

IMS启动时会调用InputReader.start()方法 InputReader.cpp status_t InputReader::start() {if (mThread) {return ALREADY_EXISTS;}mThread std::make_unique<InputThread>("InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });…

Nignx的搭建与核心配置

目录 一、Nginx是什么&#xff1f; 1、Nginx概述 2、Nginx模块与作用 3、Nginx三大作用&#xff1a;反向代理&#xff0c;负载均衡&#xff0c;动静分离 nginx七层负载均衡调度算法&#xff08;六种&#xff09; 1、轮询&#xff08;默认调度算法&#xff09; 2、加权轮…

雨量水位在线监测站

TH-SW2随着科技的不断进步&#xff0c;雨量水位在线监测站在防汛、水资源管理、环境保护等领域发挥着越来越重要的作用。传统的雨量水位监测方法往往存在精度不高、实时性不强等问题&#xff0c;而雷达式测量技术的出现&#xff0c;则为这一领域带来了革命性的变化。 一、雨量水…

【踩坑专栏】主机ping虚拟机失败

我出现的问题finalshell连接超时&#xff0c;ping了一下发现ping都ping不通&#xff0c;于是发现问题所在。 最开始我是把虚拟机的网络设置改为桥接模式&#xff0c;问题解决了&#xff0c;但是这种模式的问题就是每次开机&#xff0c;ip都会改变&#xff0c;因此非常麻烦&…

零到大师:嵌入式Linux学习书单分享

大家好&#xff0c;我是知微&#xff01; 上一篇推荐的书单嵌入式软件必读10本书_单片机篇&#xff0c;收到反响很好。再推荐一篇嵌入式Linux相关的书单。 《鸟哥的Linux私房菜》 鸟哥的Linux系列适合零基础小伙伴&#xff0c;从电脑基础到文件系统、shell脚本等等&#xff…

【PX4学习笔记】04.QGC地面站的使用

目录 文章目录 目录PX4代码烧入PX4固件代码的烧入方式1PX4固件代码的烧入方式2 QGC地面站的基础使用连接地面站的方式查看关键的硬件信息 QGC地面站的Application Settings模块Application Settings模块-常规界面单位其他设置数据持久化飞机中的数传日志飞行视图计划视图自动连…

Unity Shader ASE基础效果思路与代码(一):遮罩、硬边溶解、光边溶解、UV扰动

Unity Shader ASE基础效果思路与代码(一)&#xff1a;遮罩、硬边溶解、光边溶解、UV扰动 文章目录 Unity Shader ASE基础效果思路与代码(一)&#xff1a;遮罩、硬边溶解、光边溶解、UV扰动遮罩效果硬边溶解光边溶解UV扰动 遮罩效果 效果展示&#xff1a; 思路与代码&#xff1…

SQL注入:堆叠注入-强网杯[随便注]

目录 什么是堆叠注入&#xff1f; 强网杯-随便注 rename && alter绕过 prepare绕过 Handle绕过 靶机&#xff1a;BUUCTF在线评测 什么是堆叠注入&#xff1f; 在一些场景中&#xff0c;应用程序支持一次执行多条SQL语句&#xff0c;我们称为堆叠查询&#xff0c;…

HTML5-CSS3

一、HTML5的新特性 HTML5 的新增特性主要是针对于以前的不足&#xff0c;增加了一些新的标签、新的表单和新的表单属性等。 这些新特性都有兼容性问题&#xff0c;基本是 **IE9 以上版本的浏览器**才支持&#xff0c;如果不考虑兼容性问题&#xff0c;可以大量使用这些新特性…

前端是做什么的?

前端很多时候代表从事前端开发的工程师&#xff0c;定义上来说&#xff0c;他们负责规划、设计、构建和运行网站、软件程序和基于 Web 的应用程序的用户界面系统。通俗点来说&#xff0c;我们看到的网站、点击网站所有的按钮以及和网站的交互都是前端所进行的工作。 概述 前端…

pclpy 窗口可视化多个点云

pclpy 窗口可视化多个点云 一、算法原理二、代码三、结果1.可视化结果 四、相关数据五、问题与解决方案1.问题2.解决 一、算法原理 原理看一下代码写的很仔细的。。目前在同一个窗口最多可视化两个点云。。 二、代码 from pclpy import pcldef CloudShow(cloud1, cloud2):&q…

java数据结构与算法刷题-----LeetCode222. 完全二叉树的节点个数

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 1. 法一&#xff1a;利用完全二叉树性质&#xff0c;进行递归二分查找 解…

145. Binary Tree Postorder Traversal(二叉树的后序遍历)

问题描述 给你一棵二叉树的根节点 root &#xff0c;返回其节点值的 后序遍历 。 问题分析 此问题与二叉树的前序遍历分析类似&#xff0c;只是在数组合并的时候顺序发生一下变化就行了。 代码 int* postorderTraversal(struct TreeNode* root, int* returnSize) {if(root…

【电子书】计算机课程

资料 wx&#xff1a;1945423050 个人整理了一些互联网电子书 计算机课程 Netty权威指南&#xff08;第2版&#xff09;.epubSharePoint Server 2016 IT Pro 部署指南.epubTensorFlow自然语言处理.epubWebGIS之OpenLayers全面解析.epub从Paxos到Zookeeper分布式一致性原理与实践…