数据结构篇-01:单调栈

news/2024/7/24 3:35:47 标签: 数据结构, java, 开发语言, 单调栈

单调栈是栈的一种,可以使得每次新元素入栈后,栈内的元素都保持有序(单调递增或者单调递减)。

单调栈的用途不太广泛,只处理一类典型的问题,比如[下一个更大元素]、[上一个更小元素] 等。

在本文中,我将首先介绍 [单调栈] 的使用模板,接着我会使用单调栈的技巧来解决力扣hot100中的两道题:739、每日温度;84、柱状图中最大的矩形

单调栈的使用

例题:输入一个数组 nums,请你返回一个等长的结果数组,结果数组中对应索引存储着下一个更大元素,如果没有更大的元素,就存 -1。

函数签名如下:

int[] nextGreaterElement(int[] nums);

比如说,输入一个数组 nums = [2,1,2,4,3],你返回数组 [4,2,4,-1,-1]。因为第一个 2 后面比 2 大的数是 4; 1 后面比 1 大的数是 2;第二个 2 后面比 2 大的数是 4; 4 后面没有比 4 大的数,填 -1;3 后面没有比 3 大的数,填 -1。

java">int[] nextGreaterElement(int[] nums) {
    int n = nums.length;
    // 存放答案的数组
    int[] res = new int[n];
    Stack<Integer> s = new Stack<>(); 
    // 倒着入栈,借助栈的结构,就是正着出栈
    for (int i = n - 1; i >= 0; i--) {
        // 对元素进行判定:如果当前元素不小于栈顶元素,则弹出
        //因为我们要找的是“最近的更大元素”
        while (!s.isEmpty() && s.peek() <= nums[i]) {
            s.pop();
        }
        // nums[i] 身后的更大元素
        //如果为-1,说明没有找到合适的元素
        res[i] = s.isEmpty() ? -1 : s.peek();
        s.push(nums[i]);
    }
    return res;
}

我们用文字来描述一下这个过程:

由于是倒着入栈,所以我们从 [3] 开始。[3] 后面没有比它大的元素,返回 -1,然后将 [3] 入栈;

接着是 [4] ,此时栈不为空,栈顶元素(3)小于 [4] 被弹出,栈被置为空。所以 res[i] = -1,将 [4] 压入栈中。

接着是 [2],此时栈不为空,栈顶元素 [4] 大于 [2] 满足条件,将其记录到res数组中,然后将 [2] 压入栈中。

接着是 [1],此时栈不为空,栈顶元素 [2] 大于 [1],满足条件,将其记录到res数组中,然后将 ]1\ 压入栈中。

最后是 [2],此时栈顶元素 [1] 小于 [2],弹出;弹出后,新的栈顶元素是 [2] ,不符合条件,弹出;新的栈顶元素变为 [4],满足条件,将其记录下来。并将 [2] 压入栈中

数组nums遍历结束,返回res数组。

问题1:为什么要倒着入栈?

从后往前入栈,保证了出栈的时候,栈顶元素一定是距离当前目标元素最近的元素。这样能实现寻找 “最近的更大元素”/"下一个更大元素"

比如数组 [1,2,3,4] 。当数组倒着遍历到2的时候,栈中的元素是 [4,3],那么出栈顺序就是 [3,4]。就是这样借助栈的性质来实现查找


力扣739:每日温度

 

比如说给你输入 temperatures = [73,74,75,71,69,76],你返回 [1,1,3,2,1,0]。因为第一天 73 华氏度,第二天 74 华氏度,比 73 大,所以对于第一天,只要等一天就能等到一个更暖和的气温,后面的同理。

这个问题本质上也是找下一个更大元素,只不过现在不是问你下一个更大元素的值是多少,而是问你当前元素距离下一个更大元素的索引距离而已。

所以我们要对上面单调栈的算法模板稍加修改:之前用于存放元素的数组res中不再用于记录元素,而是记录元素的索引

java">int[] dailyTemperatures(int[] temperatures) {
    int n = temperatures.length;
    int[] res = new int[n];
    // 这里放元素索引,而不是元素
    Stack<Integer> s = new Stack<>(); 
    /* 单调栈模板 */
    for (int i = n - 1; i >= 0; i--) {
        while (!s.isEmpty() && temperatures[s.peek()] <= temperatures[i]) {
            s.pop();
        }
        // 得到索引间距
        res[i] = s.isEmpty() ? 0 : (s.peek() - i); 
        // 将索引入栈,而不是元素
        s.push(i); 
    }
    return res;
}

栈 s 中存放的是元素索引,所以当栈顶元素满足条件——温度高于当天温度时,栈顶元素意为:在这一天达到下一个更高的温度。

然后用栈顶元素减去当日日期就是相隔几天。

力扣84:柱状图中最大的矩形

对于该问题,暴力解法的思路是枚举每个柱子作为矩形的高度,并以该柱子为中心向左右延伸,直到遇到高度小于当前柱子的柱子为止。然后计算以当前柱子为高度的矩形的面积,并更新最大面积。

具体的实现步骤如下:

  1. 遍历每个柱子,将其作为矩形的高度(记为h)。
  2. 从当前柱子向左延伸,直到遇到高度小于h的柱子为止,记录左边界的位置(记为left)。
  3. 从当前柱子向右延伸,直到遇到高度小于h的柱子为止,记录右边界的位置(记为right)。
  4. 计算以当前柱子为高度的矩形的面积,即面积 = h * (right - left - 1)。
  5. 更新最大面积,如果当前面积大于最大面积,则更新最大面积。
  6. 重复以上步骤,直到遍历完所有柱子。

暴力方法的时间复杂度为 O(N^2),会超出时间限制,主要在于寻找左右边界上。而寻找左右边界,其实就是 “寻找最近的高度小于h的柱子”。可以使用单调栈解决。

需要注意的是,根据题目要求,我们要分别寻找左边界和右边界,所以我们要使用两次单调栈

java">class Solution {
    public int largestRectangleArea(int[] heights) {
        int n = heights.length;
        int[] left = new int[n];   // 存储每个柱子左边第一个小于它的柱子的索引
        int[] right = new int[n];   // 存储每个柱子右边第一个小于它的柱子的索引
        
        Deque<Integer> mono_stack = new ArrayDeque<Integer>();  
         // 单调栈,存储柱子的索引
        //我们寻找右边界时是倒着入栈,那么寻找左边界时就是正着入栈
        for (int i = 0; i < n; ++i) {
            //在这里我们要寻找的是:当前柱子左侧的第一个小于它的索引。所以如果栈顶元素大于当前柱子高度(不符合条件),则弹出该元素
            while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
                mono_stack.pop(); 
            }
            
            left[i] = (mono_stack.isEmpty() ? -1 : mono_stack.peek());   
            mono_stack.push(i);   // 将当前柱子的索引入栈
        }
        //清除栈中的元素,因为后面还要用这个栈
        mono_stack.clear();
        for (int i = n - 1; i >= 0; --i) {
            while (!mono_stack.isEmpty() && heights[mono_stack.peek()] >= heights[i]) {
                mono_stack.pop();   
            }
            right[i] = (mono_stack.isEmpty() ? n : mono_stack.peek()); 
            mono_stack.push(i);   // 将当前柱子的索引入栈
        }
        
        int ans = 0;
        for (int i = 0; i < n; ++i) {
            // 计算每个柱子的面积并更新最大面积
            ans = Math.max(ans, (right[i] - left[i] - 1) * heights[i]); 
        }
        return ans;
    }
}


http://www.niftyadmin.cn/n/5356427.html

相关文章

JSP仓储管理系统myeclipse定制开发SQLServer数据库网页模式java编程jdbc

一、源码特点 JSP仓储管理系统系统是一套完善的web设计系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库 &#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为SQLServer2008&#x…

1 月 30 日算法练习-思维和贪心

文章目录 重复字符串翻硬币乘积最大 重复字符串 思路&#xff1a;判断是否能整除&#xff0c;如果不能整除直接退出&#xff0c;能整除每次从每组对应位置中找出出现最多的字母将其他值修改为它&#xff0c;所有修改次数即为答案。 #include<iostream> using namespace …

Flink实战四_TableAPISQL

接上文&#xff1a;Flink实战三_时间语义 1、Table API和SQL是什么&#xff1f; 接下来理解下Flink的整个客户端API体系&#xff0c;Flink为流式/批量处理应用程序提供了不同级别的抽象&#xff1a; 这四层API是一个依次向上支撑的关系。 Flink API 最底层的抽象就是有状态实…

DHCP饿死攻击及防御(基于ENSP模拟器、Kali攻击机实现)

1.DHCP饿死攻击及防御(基于ENSP模拟器、Kali攻击机实现) 相关参数&#xff1a; Kali攻击机一台 ENSP模拟器 拓扑图&#xff1a; 实验说明&#xff1a; 通过配置DHCP_Server&#xff0c;使得192.168.150.0/24子网内的终端能够自动获取IP地址及DNS 通过配置SW交换机&#xff0c…

开源项目TARZAN-NAV | 基于springboot的现代化导航网站系统

TARZAN-NAV 导航网站 一个基于 Spring Boot、MyBatis-Plus、h2database、ehcache、Docker、websocket等技术栈实现的导航网站系统&#xff0c;采用主流的互联网技术架构、全新的UI设计、支持一键源码部署&#xff0c;拥有完整的仪表板、导航管理&#xff0c;用户管理、评论管理…

GoLang中应该避免的10个错误

Go是一种静态类型的、并发的、垃圾收集的编程语言&#xff0c;由谷歌开发。近年来&#xff0c;由于它的简单性、性能和对并发的强大支持&#xff0c;它已经获得了普及。尽管它很简单&#xff0c;但开发人员在编写Go代码时仍有一些常见的错误。下面是Go语言中需要避免的十大坏错…

【极数系列】Flink集成DataSource读取Socket请求数据(09)

文章目录 01 引言02 简介概述03 基于socket套接字读取数据3.1 从套接字读取。元素可以由分隔符分隔。3.2 windows安装netcat工具&#xff08;1&#xff09;下载netcat工具&#xff08;2&#xff09;安装部署&#xff08;3&#xff09;启动socket端口监听 04 源码实战demo4.1 po…

网站打不开怎么办?高防IP弹性防护更省心

不管你是什么网站&#xff0c;商城网站、游戏网站或者支付网站都有可能存在被攻击的情况&#xff0c;超过防护就会被打死&#xff0c;网站随即而来就打不开了。网站打不开怎么办&#xff1f;看看是不是网站主机或者服务器被攻击了。攻击的大小不可控&#xff0c;选择高防服务器…