单调栈简介

单调栈简介何为单调栈栈内元素非递增或者非递减。另一种说法是从栈底到栈顶非递增或者非递减。在很多情况下,可能会出现相同的数字元素,所以称之为非递增或者非递减栈更合适。显而易见,从单调栈的这种结构很容易联想到,在算法中,合理运用单调栈,能够将O(n^2)的时间复杂度优化到O(n),这就是技巧。相对的,空间复杂度会增加,因为需要动态维护一个栈。这里需要明白一点,算法里面,都是时间和空间的取舍,所谓的时空间转换指的就是这个,所以要根据具体场景去选择。适用范围求一个数组每一个的下一个最大值、对一个数组排序、判断当前元素

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

何为单调栈

栈内元素非递增或者非递减。另一种说法是从栈底到栈顶递增或者递减。在很多情况下,可能会出现相同的数字元素,所以称之为非递增或者非递减栈比递增、递减栈更合适。

显而易见,从单调栈的这种结构很容易联想到,在算法中,合理运用单调栈,能够将O(n^2)的时间复杂度优化到O(n),这就是技巧。相对的,空间复杂度会增加,因为需要动态维护一个栈。这里需要明白一点,算法里面,都是时间和空间的取舍,所谓的时空间转换指的就是这个,所以要根据具体场景去选择。

适用范围

求一个数组元素的下一个最大或最小值、判断当前元素符合某种条件的左右边界…

第一个例子,求第一个数组中的x元素在第二个数组中的位置右边的第一大元素。

对应leetcode:下一个更大元素 I

数组1:[4,2,1],数组2:[1,3,4,2]。很明显,要求的是右边的第一大元素,说明需要先知道当前元素右边的数字,所以从右边开始遍历比较方便;反之,如果要求当前元素左边的第一大元素,那么就从左往右遍历(重要)。

解法1的思想是,从右往左遍历,循环出栈后,就认为栈顶元素就是下一个最大的值。过程:

  1. 栈空,2入栈
  2. 4 > 2,2出栈,栈为空,没有比4大的元素,4入栈
  3. 3 < 4,且栈不空,栈顶元素4即为3的右边第一大元素,3入栈
  4. 1 < 3,且栈不空,栈顶元素3即为1的右边第一大元素,1入栈

代码:

void demo() {
    int[] nums1 = {4, 1, 2};
    int[] nums2 = {1, 3, 4, 2};
    int[] res = new int[nums1.length];
    HashMap<Integer, Integer> map = new HashMap<>();
    Deque<Integer> stack = new LinkedList<>();
    for (int i = nums2.length - 1; i >= 0; --i) {
        while (!stack.isEmpty() && nums2[i] >= stack.peek()) {
            stack.pop();
        }
        map.put(nums2[i], stack.isEmpty() ? -1 : stack.peek());
        stack.push(nums2[i]);
    }
    for (int i = 0; i < nums1.length; ++i) {
        res[i] = map.get(nums1[i]);
    }
    for (int ele : res) {
        System.out.print(ele + "  ");
    }
}

解法2的思想是,仍然从左往右遍历,当栈不空或者栈顶元素比当前元素小,循环出栈,出栈元素的下一个最大值即为当前遍历的值。过程:

  1. 栈空,1入栈
  2. 栈不空且3 > 1,1出栈,1的下一个最大元素为3,3入栈
  3. 栈不空且4 > 3,3出栈,3的下一个最大元素为4,4入栈
  4. 栈不空,但2 < 4,4的下一个最大元素为-1,2入栈
  5. 栈内元素全部没有右边的下一个最大元素,都为-1

代码:

void demo() {
    int[] nums1 = {4, 1, 2};
    int[] nums2 = {1, 3, 4, 2};
    int[] res = new int[nums1.length];
    HashMap<Integer, Integer> map = new HashMap<>();
    Deque<Integer> stack = new LinkedList<>();
    for (int i = 0; i < nums2.length; ++i) {
        while (!stack.isEmpty() && nums2[i] > stack.peek()) {
            map.put(stack.pop(), nums2[i]);
        }
        stack.push(nums2[i]);
    }
    for (int i = 0; i < nums1.length; ++i) {
        res[i] = map.getOrDefault(nums1[i], -1);
    }
    for (int e : res) {
        System.out.print(e + "  ");
    }
}

入门第二个例子,求nums 中每个元素的下一个更大元素,这意味着应该循环去遍历数组。如数组[1,2,1],直接从前往后遍历,使用上个例题中的第二个方法,但是因为不需要元素的对应关系,所以我们可以单调栈中存下标。过程由于是存下标,就不写了。

对应leetcode:下一个更大元素 II

代码如下:

void demo() {
    int[] nums = {1, 2, 1};
    int n = nums.length;
    Deque<Integer> stack = new LinkedList<>();
    int[] res = new int[n];
    Arrays.fill(res, -1);
    for (int i = 0; i < 2 * n - 1; ++i) {
        while (!stack.isEmpty() && nums[i % n] > nums[stack.peek()]) {
            res[stack.pop()] = nums[i % n];
        }
        stack.push(i % n);
    }
    for (int e : res) {
        System.out.print(e + " ");
    }
}  

最后再来一个求每个元素左右边界的例子。

对应leetcode:84. 柱状图中最大的矩形

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。 例如[2,1,5,6,2,3],直接从左往右遍历,找到每个元素的左边界;再从右往左遍历,找到每个元素的右边界。那么每个元素的左右边界宽度与当前元素的乘积即为当前元素所能组成的最大区间,比较每个元素所能组成的区间面积即可得到整个数组的最大区间。从左往右过程:

  1. 栈空,2入栈,2的左边界是-1
  2. 栈不空,且1 < 2,2出栈,栈空,1的左边界是-1,1入栈
  3. 栈不空,且5 > 1,栈不空,5的左边界是1,5入栈
  4. 栈不空,且6 > 5,栈不空,6的左边界是2,6入栈
  5. 栈不空,且2 < 6,6出栈,5出栈,2的左边界是1,2入栈
  6. 栈不空,且3 > 2,栈不空,3的左边界是4,3入栈

如此,即可得到数组每一个元素的左边界,相同的方法,求出右边界。

代码:

public int largestRectangleArea(int[] heights) {
    int n = heights.length;
    Deque<Integer> stack = new LinkedList<>();
    int[] leftArray = new int[n];
    int[] rightArray = new int[n];
    for (int i = 0; i < n; ++i) {
        while (!stack.isEmpty() && heights[i] <= heights[stack.peek()]) {
            stack.pop();
        }
        leftArray[i] = stack.isEmpty() ? -1 : stack.peek();
        stack.push(i);
    }
    stack.clear();
    for (int i = n - 1; i >= 0; --i) {
        while (!stack.isEmpty() && heights[i] <= heights[stack.peek()]) {
            stack.pop();
        }
        rightArray[i] = stack.isEmpty() ? n : stack.peek();
        stack.push(i);
    }
    int res = 0;
    for (int i = 0; i < n; ++i) {
        res = Math.max(res, (rightArray[i] - leftArray[i] - 1) * heights[i]);
    }
    return res;
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/190746.html原文链接:https://javaforall.net

(0)
上一篇 2026年2月14日 下午8:15
下一篇 2026年2月14日 下午8:43


相关推荐

  • PHP环境搭建-Windows系统下PHP环境搭建

    PHP环境搭建-Windows系统下PHP环境搭建1 PHP 环境搭建的前提是 ApacheHTTPSe Apache 服务器 已经安装部署成功 并可以正常访问到服务器的主页面 ApacheHTTPSe 的安装部署已经在上一篇讲解的很详细了 不清楚的可以点击 nbsp ApacheHTTPSe Apache 服务器下载与 Windows 系统下安装 查看具体操作 2 PHP 下载 nbsp nbsp 2 1 下载地址 http

    2026年3月19日
    1
  • 史上最全,最详细的Python入门教程!你应该没见过比这篇还详细的

    史上最全,最详细的Python入门教程!你应该没见过比这篇还详细的很多学 Python 学的不错的小伙伴都有碰到这样的事情吧 就是自己还没毕业就有很多的企业来聘请你去他们的公司面试入职 我身边就有一个活生生的例子 我一位学长 学了三四年的 Python 毕业了去阿里面试 居然直接就被应聘上了 后来他和我们分享他的入职经验 就是把自己所学会的东西尽量做到最完美 做到最美观 代码是写给人看的 所以代码的美观性在 HR 那里是非常重要的 大家都好好加油 看的我们确实羡慕的要死

    2026年3月26日
    1
  • linux系统移植的一般过程_内核移植的基本步骤

    linux系统移植的一般过程_内核移植的基本步骤在众多嵌入式操作系统中,Linux目前发展最快、应用最为广泛。性能优良、源码开放的Linux具有体积小、内核可裁减、网络功能完善、可移植性强等诸多优点,非常适合作为嵌入式操作系统。一个最基本的Linux操作系统应该包括:引导程序、内核与根文件系统三部分。  嵌入式Linux系统移植主要由四大部分组成:  一、搭建交叉开发环境  二、bootloader的选择和移植  三、kernel的配置、编译、…

    2026年3月10日
    6
  • Python中的输出「建议收藏」

    Python中的输出「建议收藏」1.Python的输出语句Python输出语句是print,但是python2.x与3.x又有点区别。python2.x输出print"xxx"能成功执行,而3.x不行,所以

    2022年7月5日
    26
  • 计算机网络实验——FTP服务器配置(使用windows IIS服务)

    计算机网络实验——FTP服务器配置(使用windows IIS服务)基于 Win10 系统 打开控制面板 在控制面板窗口中 找到 程序 点击 在打开的 程序 窗口中 找到 启用或关闭 windows 功能 点击打开 如下图所示 san5 在 windows 功能 中找到 InternetInfo 并选中 Web 管理工具 万维网服务 点击 确定 系统自动配置成功后 按下 win X 快捷键 选中计算机管

    2026年3月17日
    0
  • 成为java架构师需要具备那些技能?

    成为java架构师需要具备那些技能?架构师定义百度百科,系统架构师是一个既需要掌控整体又需要洞悉局部瓶颈并依据具体的业务场景给出解决方案的团队领导型人物。架构师工作职能软件架构师在整个软件开发过程中都起着重要的作用,并随着开发进程的推进而其职责或关注点不断地变化,在需求阶段,软件架构师主要负责理解和管理非功能性系统需求,比如软件的可维护性、性能、复用性、可靠性、有效性和可测试性等等,此外,架构师还要经常审查客户及市场人员

    2022年7月8日
    21

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号