单调栈图文详解(附Java模板)

article/2025/9/30 19:50:06

在这里插入图片描述

                                                                             啥是"单调栈",它能解决什么样的问题?          


🌷 仰望天空,妳我亦是行人.✨
🦄 个人主页——微风撞见云的博客🎐
🐳 数据结构与算法专栏的文章图文并茂🦕生动形象🦖简单易学!欢迎大家来踩踩~🌺
🪁 希望本文能够给读者带来一定的帮助🌸文章粗浅,敬请批评指正!🐥


文章目录

  • 🦩单调栈的概念
  • 🐸适用场景
  • 🦕情形示例
  • 🦄模板例题 —— 洛谷 P5788 【模板】单调栈
  • 🐲进阶例题 —— LeetCode 42. 接雨水
  • 🐳结语


🦩单调栈的概念

🍐单调栈分为单调递增栈单调递减栈,通过使用单调栈我们可以访问到最近一个比它大(小)的元素

    🍊 单调递增栈:单调递栈就是从栈底到栈顶数据是依次递增,通常是寻找某方向第一个比它小的元素
    🍊 单调递减栈:单调递栈就是从栈底到栈顶数据是依次递减,通常是寻找某方向第一个比它大的元素

🐸适用场景

🍋 什么情况适合用单调栈来解决实际问题呢?

    🍒 通常是在数组中需要通过比较前后元素的大小关系来找最近的比它大(小)的元素问题时,可以使用单调栈进行求解。

🦕情形示例

        🐬1. 寻找左边第一个小于它的数

                  🐟题目描述: 给定一个长度为 n ≤ 10 ^5 的数组 a,输出每个数左边第一个比它小的数,如果不存在则输出 − 1。

        🦕【常规思路】

                  🦖双重循环来做,第一重循环枚举每个数,第二重循环找出指定区间类第一个满足条件的数。然而这种做法的复杂度是O(n^2)利用单调栈,我们可以将复杂度降低至O(n)。

    🐸在指针 i 从左往右遍历的过程中,我们可以用一个栈来保存 i 左边的所有元素(不包括i指向的元素),下标越大的元素越接近栈顶下标越小的元素越接近栈底

    🐢每次我们访问栈顶,只要栈顶元素大于等于 a [ i ],我们就将栈顶元素弹出直至栈顶元素小于 a [ i ] ,此时输出栈顶元素并将 a [ i ] 压入栈中。 由于栈中保存了 i 左边的所有元素,所以只要有答案,则答案一定在栈中

     🐉由于每个元素一定会被压入一次且至多弹出一次,因此操作次数至多是2n,故总时间复杂度为O(n)。


            🐳🐳🐳🐳🐳🐳🐳🐳🐳🐳🐳🐳🐳让我们来看看过程图解🐳🐳🐳🐳🐳🐳🐳🐳🐳🐳🐳🐳

        🦩初始化 原数组结果数组,我们去寻找最右边的数字5左边最近的、小于它的数值

在这里插入图片描述

        🦩准备第一个元素“2” 入栈,由于栈空,咱们直接修改结果数组第一个元素值为-1(默认填充了-1,所以我们这里就不修改了)。然后将元素入栈。

在这里插入图片描述
        🦩准备将第二个元素“4” 入栈,此时栈非空,但是栈顶元素小于当前元素,所以,记录结果数组对应值为 栈顶元素的值然后入栈当前元素
在这里插入图片描述
        🦩准备将第三个元素“1” 入栈,此时栈非空,并且栈顶元素大于当前元素,所以我们应该依次弹栈直到栈顶元素小于当前元素或者栈空记录结果数组对应值为栈顶元素的值(这里已经栈空了,所以填充-1),然后入栈当前元素
在这里插入图片描述
        🦩准备将第四个元素“3” 入栈,此时栈非空,并且栈顶元素小于当前元素,所以记录结果数组对应值为 栈顶元素的值,然后入栈当前元素
在这里插入图片描述
        🦩准备将第五个元素“6” 入栈,此时栈非空,并且栈顶元素小于当前元素,所以记录结果数组对应值为 栈顶元素的值,然后入栈当前元素

在这里插入图片描述
        🦩准备将最后一个元素“5” 入栈,此时栈非空,并且栈顶元素大于当前元素,所以我们应该依次弹栈直到栈顶元素小于当前元素或者栈空记录结果数组对应值为栈顶元素的值(这里只弹出一个元素就满足了,并且栈非空,所以填充栈顶元素即可),然后入栈当前元素此时得到的结果数组即为最终结果.
在这里插入图片描述

        🌸Java代码如下:

public class Main {static int N = (int) (1e5 + 10);static int[] a = new int[N], ans = new int[N];static Deque<Integer> stack = new LinkedList<>();static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));public static void main(String[] args) throws IOException {in.nextToken();int n = (int) in.nval;for (int i = 0; i < n; i++) {//存数组in.nextToken();a[i] = (int) in.nval;}for (int i = 0; i < n; i++) {//单调栈模板(注意是数值)while (!stack.isEmpty() && stack.peekFirst() >= a[i]) stack.poll();if (!stack.isEmpty()) ans[i] = stack.peekFirst();else ans[i] = -1;stack.push(a[i]);}for (int i = 0; i < n; i++) {//输出结果System.out.print(ans[i] + " ");}}
}

        💮💮💮💮💮💮💮💮💮💮💮💮💮💮💮下面,我们再来看看其他几种情况,基本上都是大同小异。💮💮💮💮💮💮💮💮💮💮💮💮💮💮💮💮💮💮

        🐬2. 寻找左边第一个小于它的数的下标

                  🐟题目描述: 给定一个长度为 n ≤ 10 ^5 的数组 a,输出每个数左边第一个比它小的数的下标,如果不存在则输出 − 1。

                  🦕我们只需要注意几个点,在当前条件下,咱们栈中存的是下标,而不是值,所以需要修改两个地方:a[stack.peekFirst()] 而不是stack.peekFirst()不再是a[i],而是存储对应的下标,具体要修改的地方我已经在注释里写出来了。

        🌸Java代码如下:

public class Main {static int N = (int) (1e5 + 10);static int[] a = new int[N], ans = new int[N];static Deque<Integer> stack = new LinkedList<>();static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));public static void main(String[] args) throws IOException {in.nextToken();int n = (int) in.nval;for (int i = 0; i < n; i++) {//存数组in.nextToken();a[i] = (int) in.nval;}for (int i = 0; i < n; i++) {//单调栈模板(注意是下标)while (!stack.isEmpty() && a[stack.peekFirst()] >= a[i]) stack.poll();//注意这里的第二个条件是a[stack.peekFirst()] 而不是stack.peekFirst()if (!stack.isEmpty()) ans[i] = stack.peekFirst();else ans[i] = -1;stack.push(i);//这里也不再是a[i],而是存储对应的下标}for (int i = 0; i < n; i++) {//输出结果System.out.print(ans[i] + " ");}}
}

        🐬3. 寻找右边第一个大于它的数

                  🐟题目描述: 给定一个长度为 n ≤ 10 ^5 的数组 a,输出每个数右边第一个比它大的数,如果不存在则输出 − 1。

                  🦕之前我们是在一个数的左边去寻找,所以让栈去保存这个数左边的所有数,类似地,现在需要让栈去保存这个数右边的所有数。
考虑将数组翻转(倒序遍历),因此情形三变成了「寻找一个数左边第一个大于它的数」属于情形一

        🌸Java代码如下:

public class Main {static int N = (int) (1e5 + 10);static int[] a = new int[N], ans = new int[N];static Deque<Integer> stack = new LinkedList<>();static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));public static void main(String[] args) throws IOException {in.nextToken();int n = (int) in.nval;for (int i = 0; i < n; i++) {//存数组in.nextToken();a[i] = (int) in.nval;}for (int i = n - 1; i >= 0; i--) {//单调栈模板(注意是数值)while (!stack.isEmpty() && stack.peekFirst() <= a[i]) stack.poll();if (!stack.isEmpty()) ans[i] = stack.peekFirst();else ans[i] = -1;stack.push(a[i]);}for (int i = 0; i < n; i++) {//输出结果System.out.print(ans[i] + " ");}}
}

        🐬4. 寻找右边第一个大于它的数的下标

                  🐟题目描述: 给定一个长度为 n ≤ 10 ^5 的数组 a,输出每个数右边第一个比它大的数的下标,如果不存在则输出 − 1。

                  🦕结合情形二和情形三即可写出代码。

        🌸Java代码如下:

public class Main {static int N = (int) (1e5 + 10);static int[] a = new int[N], ans = new int[N];static Deque<Integer> stack = new LinkedList<>();static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));public static void main(String[] args) throws IOException {in.nextToken();int n = (int) in.nval;for (int i = 0; i < n; i++) {//存数组in.nextToken();a[i] = (int) in.nval;}for (int i = n-1; i >= 0; i--) {//单调栈模板(注意是下标)while (!stack.isEmpty() && a[stack.peekFirst()] <= a[i]) stack.poll();if (!stack.isEmpty()) ans[i] = stack.peekFirst();else ans[i] = -1;stack.push(i);}for (int i = 0; i < n; i++) {//输出结果System.out.print(ans[i] + " ");}}
}

🥕总结以上情形:
    🍏遍历顺序(以怎样的顺序遍历数组 a );
    🍏比较方式(如何比较当前元素和栈顶元素);
    🍏栈中存储的是什么(是元素本身还是元素的下标)。

🦄模板例题 —— 洛谷 P5788 【模板】单调栈

洛谷 P5788 【模板】单调栈
在这里插入图片描述
🌸Java代码如下:(不知道为啥,Java的没AC,C++这样写是AC的)

public class Main {static int N = (int) (3e6 + 10);static int[] a = new int[N], ans = new int[N];static Deque<Integer> stack = new LinkedList<>();static StreamTokenizer in = new StreamTokenizer(new BufferedReader(new InputStreamReader(System.in)));public static void main(String[] args) throws IOException {in.nextToken();int n = (int) in.nval;for (int i = 1; i <= n; i++) {//存数组in.nextToken();a[i] = (int) in.nval;}for (int i = n; i > 0; i--) {//单调栈模板(注意是下标)while (!stack.isEmpty() && a[stack.peekFirst()] <= a[i]) stack.poll();if (!stack.isEmpty()) ans[i] = stack.peekFirst();stack.push(i);}for (int i = 1; i <= n; i++) {//输出结果System.out.print(ans[i] + " ");}}
}

🐲进阶例题 —— LeetCode 42. 接雨水

42. 接雨水

在这里插入图片描述
    🍏思路:
        🐳遍历heights数组,将其中的元素加入单调递减栈,如果当前柱子的高度大于栈顶柱子的高度,不断出栈,相当于找到左边比当前柱子矮的位置,然后每次出栈之后都要累加一下面积。

    🐸复杂度:
        🦕时间复杂度O(n),n是heights的长度,数组中的每个元素最多入栈出栈一次。
        🦕空间复杂度O(n),栈的空间,最多不会超过heights的长度

    🌸Java代码如下:(相比之前的代码有些许变化,因为这道题需要做的事情会稍微多一点,注释我打在了代码中,请大家耐心阅读

import java.util.Deque;
import java.util.LinkedList;public class Main {public static void main(String[] args) {int[] height = {0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1};System.out.println(trap(height));}public static int trap(int[] height) {
//        int[] height = {0, 1, 0, 2, 1, 0, 1, 3, 2, 1, 2, 1};int ans = 0;//总雨水量Deque<Integer> stack = new LinkedList<>();int n = height.length;for (int i = 0; i < n; ++i) {//这里是在干这样一件事情:把当前的这根柱子作为“右柱”,把栈顶的元素作为“中间柱”也叫“接水柱”//(此时还没弹栈),然后把“接水柱”前面的那个柱子,作为“左柱”,有了“左柱”和“右柱”,//咱们的“接水柱”就能接水了,但是它只能接到左右两边更低的那个柱子高度的水。while (!stack.isEmpty() && height[stack.peek()] <= height[i]) {
//上面这个式子说明:“右柱”比栈顶也就是“接水柱”更高,这样的话才能准备接水。
//否则的话,就是满足单调递减栈的,那么我们继续入栈。int top = stack.pop();//拿出前一个柱子if (stack.isEmpty()) break;//如果拿出这根柱子后,前面没有元素了,那就接不了雨水了,因为接雨水的话,至少需要左右两边都有柱子才行。int left = stack.peek();//记录一下拿到的这根柱子的左边那根柱子的高度int currWidth = i - left - 1;//看图推算。
//上面这个式子有人会说:不都是1吗?其实不是的,加入我们连续加入两个0高度的柱子(有点奇怪),
//这个时候,不符合单调栈的定义,那么我们会弹出一个栈,但是由于高度为0,我们也不会因此得到更多的面积,
//因为s = h * w; 不过,这个时候你会发现,中间空出来了一个,准确的说是两格,
//因为前面还有一个0高度的柱子,那么我们下次找到“右柱”的时候就会发现:这个宽度并非是1,
//而是隔开了一定的距离,这个距离和下标有关,看图稍加推导得出距离为:i - left - 1;int currHeight = Math.min(height[left], height[i]) - height[top];//用左右两边更小的柱子来接雨水(木桶原理)ans += currWidth * currHeight;//记录本次所接的雨水量}stack.push(i);//经过上面一顿操作之后,咱们的栈又满足单调性了,于是将当前元素的下标入栈。}return ans;}}

在这里插入图片描述

🐇 我知道,看到这里的你一定特别不容易!!!祝你收获满满,更上一层楼~ 🐇


🐳结语

    🐬初学一门技术时,总有些许的疑惑,别怕,它们是我们学习路上的点点繁星,帮助我们不断成长。

    🐟文章粗浅,希望对大家有帮助!

    🐠参考文章:单调栈详解、单调栈


http://chatgpt.dhexx.cn/article/I9AFwd9o.shtml

相关文章

算法之单调栈常见题目

什么时候需要使用单调栈&#xff1f; 通常是一维数组&#xff0c;要寻找任意一个右边或者左边第一个比自己大或小的元素的位置&#xff0c;此时我们就想到可以使用单调栈了。 单调栈的本质是空间换时间&#xff0c;因为在遍历的过程中需要用一个栈来记录右边第一个比当前元素高…

单调栈及单调栈的应用

什么是单调栈 单调递增栈&#xff1a;单调递增栈就是从栈底到栈顶数据是从大到小单调递减栈&#xff1a;单调递减栈就是从栈底到栈顶数据是从小到大 解决那类问题 要知道单调栈的适用于解决什么样的问题&#xff0c;我们首先需要知道单调栈的作用。单调栈分为单调递增栈和单调…

理解单调栈与单调队列

单调栈 单调栈&#xff1a;栈内的元素按照某种方式排序下单调递增或单调递减&#xff0c;如果新入栈的元素破坏的单调性&#xff0c;就弹出栈内元素&#xff0c;直到满足单调性。 单调栈分为单调递增栈和单调递减栈&#xff1a; 单调递增栈&#xff1a;栈中数据入栈或出栈的…

【栈 单调栈】浅谈单调栈与单调栈的理解

单调栈 定义&#xff1a; 单调栈&#xff0c;顾名思义&#xff0c;是栈内元素保持一定单调性&#xff08;单调递增或单调递减&#xff09;的栈。这里的单调递增或递减是指的从栈顶到栈底单调递增或递减。既然是栈&#xff0c;就满足后进先出的特点。与之相对应的是单调队列。 …

单调栈(一)

单调栈基本概念及实现 方案1&#xff1a;对于每一个数&#xff0c;遍历其左右位置&#xff0c;时间复杂度为O(N^2) 方案2&#xff1a;单调栈&#xff0c;每个元素入栈一次出栈一次&#xff0c;时间复杂度为O(N) &#xff08;一&#xff09;数组中没有重复值 示例&#xff1a;[…

第九章:单调栈与单调队列

单调栈与单调队列 一、单调栈1、什么是单调栈&#xff1f;2、单调栈的模板&#xff08;1&#xff09;问题&#xff1a;&#xff08;2&#xff09;分析&#xff1a; 二、单调队列1、什么是单调队列2、单调队列模板&#xff08;1&#xff09;问题&#xff08;2&#xff09;分析 一…

单调栈算法详解

单调栈算法详解 单调栈使用模板 stack<int> st; //此处一般需要给数组最后添加结束标志符&#xff0c;具体下面例题会有详细讲解 for (遍历这个数组){if (栈空 || 栈顶元素大于等于当前比较元素){入栈;}else{while (栈不为空 && 栈顶元素小于当前元素){栈顶元素…

单调队列和单调栈详解

这里是我的blog&#xff1a;有更多算法分享。排版可能也会更好看一点v https://endlesslethe.com/monotone-queue-and-stack-tutorial.html 前言 单调栈和单调队列算是栈和队列的高级应用吧&#xff0c;在公司面试中应该是不怎么会出现的&#xff08;除非算法岗&#xff1f;…

什么是单调栈

什么是单调栈 单调栈就是单调递增或者单调递减的栈&#xff0c;也就是栈底到栈顶递增或递减&#xff0c;根据单调栈的的这种结构&#xff0c;可以很容易想到运用单调栈可以很容易的把O(n)的时间复杂度优化到O(n),如果使用数组的话&#xff0c;相对的空间复杂度也不会太高 示例 …

Java实现之单调栈

目录 一.单调栈 二.每日温度 1.题目描述 2.问题分析 3.代码实现 三.下一个更大元素 I 1.题目描述 2.问题分析 3.代码实现 四.下一个更大元素 II 1.题目描述 2.问题分析 3.代码实现 一.单调栈 通常是一维数组&#xff0c;要寻找任一个元素的右边或者左边第一个比自…

[数据结构]单调栈

单调栈 这是笔者的第一篇博客&#xff0c;由于笔者自身水平的限制。用词可能不够准确&#xff0c;句子不太通顺&#xff0c;代码水平可能也不太行&#xff0c;敬请指出&#xff0c;感激不尽&#xff01; 我们都知道栈&#xff08;Stack&#xff09;是一种先入后出的数据结构&am…

单调栈和单调队列

本文摘自博客&#xff0c;欢迎前往博客以获得更好的体验。 单调栈 从名字上就听的出来&#xff0c;单调栈中存放的数据应该是严格单调有序的&#xff0c;具有以下两个性质。 满足从栈顶到栈底的元素具有严格的单调递增或单调递减性&#xff1b;满足栈的后进先出特性&#xff…

数据结构之单调栈(含代码实现)

目录 1.单调栈的基本概念 &#xff1a; 2.单调栈的应用 2.1单调栈 2.2单调栈进阶 2.3最大矩形面积 2.4最大矩形 2.5统计全为1的子矩阵数量 ​ 1.单调栈的基本概念 &#xff1a; 相信大家对栈都非常的熟悉&#xff1f;栈有一个非常鲜明的特点&#xff1a;先进后出 而所谓 单调栈…

C++之单调栈

单调栈的性质 单调栈是一种特殊的栈&#xff0c;特殊之处在于栈内的元素都保持一个单调性。 假设下图是一个栈内元素的排列情况(单调递增的栈)&#xff1a; 此时插入情况有两种&#xff1a; &#xff08;1&#xff09;插入元素大于栈顶元素&#xff1a; 因为7 > 6&#xf…

单调栈以及单调栈的应用

文章目录 单调栈的概念单调栈的应用CD101 单调栈结构&#xff08;无重复值&#xff09;CD188 单调栈结构(有重复值)496. 下一个更大元素 I739. 每日温度1856. 子数组最小乘积的最大值84. 柱状图中最大的矩形85. 最大矩形1504. 统计全 1 子矩形907. 子数组的最小值之和1307 验证…

单调栈完全解析

目录 单调栈的应用场景 为什么要使用单调栈&#xff1f; 单调栈作用的基本过程 单调栈的实现方式 栈里面的元素存放数字下标&#xff08;无重复元素&#xff09; 栈里面的元素存放数字下标组成的链表 &#xff08;有重复元素&#xff09; 单调栈的应用题目 直方图类型 …

单调栈与单调队列

文章目录 单调栈与单调队列一、单调栈1.单调递增栈2.单调递减栈总结 二、单调队列(单调双端队列) 单调栈与单调队列总结&#xff1a; 单调栈与单调队列 单调栈就是栈内元素满足单调性的栈结构。此处的单调性分为单调递增与单调递减 如何维护一个单调栈&#xff1a; 单调递增栈…

详解单调栈算法

前言 嘿&#xff01;彩蛋&#xff01;感觉有帮助就三连呗&#xff01; 如果你对这篇文章可感兴趣&#xff0c;可以点击「【访客必读 - 指引页】一文囊括主页内所有高质量博客」&#xff0c;查看完整博客分类与对应链接。 栈属于基础数据结构之一&#xff0c;基础到仅用「后进…

单调栈

定义&#xff1a; 单调栈&#xff0c;顾名思义就是栈内元素单调按照递增(递减)顺序排列的栈。 单调递增栈&#xff1a; ①在一个队列中针对每一个元素从它右边寻找第一个比它小的元素 ②在一个队列中针对每一个元素从它左边寻找第一个比它小的元素 单调递减栈&#xff1a; …

单调栈(C/C++)

目录 1. 单调栈的定义 2. 单调栈的常见用途 3. 案例分析 3.1 暴力解法 3.2 单调栈 4. 单调栈总结 1. 单调栈的定义 单调栈顾名思义&#xff0c;就是栈内的元素是单调的。根据栈内元素的单调性的不同&#xff0c;可以分为&#xff1a; 单调递增栈&#xff1a;栈内元素是单…