数据结构篇-01:单调栈

news2024/10/1 19:38:52

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

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

在本文中,我将首先介绍 [单调栈] 的使用模板,接着我会使用单调栈的技巧来解决力扣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。

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中不再用于记录元素,而是记录元素的索引

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的柱子”。可以使用单调栈解决。

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

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.coloradmin.cn/o/1413151.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

React进阶 - 11( 说一说 PropTypes 和 DefaultProps )

本章内容 目录 PropTypesDefaultProps 截止到上一节的内容&#xff0c;我们使用了一个 TodoList的案例&#xff0c;大概了解了 React的一些入门知识。从本节内容开始&#xff0c;我们将进入React进阶知识的学习 PropTypes 在组件拆分时&#xff0c;我们知道每个组件都有自己的…

将Html页面转换为Wordpress页面

问题&#xff1a;我们经常会从html源码下载网站上获得我们想要的网站内容框架&#xff0c;以及部分诸如联系我们&#xff0c;About 等内页&#xff0c;但是在文章的发布上&#xff0c;则远不如Wordpress简便。而Wordpress尽管有各种模板&#xff0c;但修改又比较麻烦。解决方法…

linux操作系统网络编程套接字(实现一个udp通讯demo)

文章目录 理解源IP地址和目的IP地址认识端口号理解 "端口号" 和 "进程ID理解源端口号和目的端口号 认识TCP协议认识UDP协议什么是面向字节流和面向数据报流以及有无连接是什么意思 网络字节序socket编程接口socket 常见APIsockaddr结构sockaddr_in 结构in_addr结…

Leetcode300. 最长递增子序列

Every day a Leetcode 题目来源&#xff1a;300. 最长递增子序列 解法1&#xff1a;递归 枚举 nums[i] 作为最长递增子序列的末尾元素&#xff0c;那么需要枚举 nums[j] 作为最长递增子序列的倒数第二个元素&#xff0c;其中 j<i 并且 nums[j]<nums[i]。 问题转化为…

python-自动化篇-运维-监控-简单实例-道出如何使⽤Python进⾏网络监控?

如何使⽤Python进⾏⽹络监控&#xff1f; 使⽤Python进⾏⽹络监控可以帮助实时监视⽹络设备、流量和服务的状态&#xff0c;以便及时识别和解决问题。 以下是⼀般步骤&#xff0c;说明如何使⽤Python进⾏⽹络监控&#xff1a; 选择监控⼯具和库&#xff1a;选择适合⽹络监控需…

IntelliJ IDEA 2023.2.5安装教程

文章结尾提供安装包获取方式 下载安装包 1. 选择相应的安装包&#xff0c;进行安装。如果已经安装过&#xff0c;可以先卸载&#xff0c;也可以保留。根据个人喜好。 2. 修改安装路径。Next……. 3. 创建桌面打开文件。Next…… 4. 等待安装结束。 5. 安装完成。 获取方式&…

文本检索性能提升 40 倍,Apache Doris 倒排索引深度解读

在 OLAP 领域&#xff0c;Apache Doris 已成为高性能、高并发以及高时效性的代名词。在面向海量数据的复杂查询需求时&#xff0c;除硬件配置、集群规模、网络带宽等因素外&#xff0c;提升性能的核心在于如何最大程度地降低 SQL 执行时的 CPU、内存和 IO 开销&#xff0c;而这…

C++知识点笔记

二维数组 定义方式&#xff1a; 1、数据类型 数组名[行数][列数]; 2、数据类型 数组名[行数][列数]{{数据1,数据2},{数据3,数据4}}; 3、数据类型 数组名[行数][列数]{数据1,数据2,数据3,数据4}; 4、数据类型 数组名[][列数]{数据1,数据2,数据3,数据4}; 建议&#xff1a;以…

在人工智能时代,如何利用AI达到行业领先地位?

人工智能很快将成为企业开展业务的一个必要环节。各企业都会具备AI战略&#xff0c;就像其具有社交媒体战略、品牌战略和人才战略等一样。 因此&#xff0c;如果企业希望在竞争中脱颖而出、获得优势&#xff0c;不能只是使用AI&#xff0c;而是要以AI为先导&#xff0c;创造行业…

OpenHarmony—类型转换仅支持as T语法

规则&#xff1a;arkts-as-casts 级别&#xff1a;错误 在ArkTS中&#xff0c;as关键字是类型转换的唯一语法&#xff0c;错误的类型转换会导致编译时错误或者运行时抛出ClassCastException异常。ArkTS不支持使用语法进行类型转换。 当需要将primitive类型&#xff08;如num…

持续集成工具Jenkins的使用之配置篇(二)

上一篇 &#xff1a;持续集成工具Jenkins的安装配置之安装篇(一)-CSDN博客 接上文 三.Jenkins配置 Jenkins配置主要是针对创建构建任务前做的一些基本配置&#xff0c;这些配置有些是必须的&#xff0c;有些是可以帮我们提高效率的&#xff0c;总之都是Jenkins管理员都要会的…

CHS_06.2.2.4_2+调度算法:时间片轮转、优先级、多级反馈队列

CHS_06.2.2.4_2调度算法&#xff1a;时间片轮转、优先级、多级反馈队列 知识总览时间片轮转&#xff08;RR, Round-Robin&#xff09;例题例题时间片轮转&#xff08;RR, Round-Robin&#xff09;优先级调度算法例题优先级调度算法思考多级反馈队列调度算法多级反馈队列调度算法…

【算法练习】leetcode算法题合集之动态规划篇

普通动规系列 LeetCode343. 整数拆分 LeetCode343. 整数拆分 将10的结果存在索引为10的位置上&#xff0c;需要保证数组长度是n1&#xff0c;索引的最大值是n&#xff0c;索引是从0开始的。 n的拆分&#xff0c;可以拆分为i和n-i&#xff0c;当然i可以继续拆分。而且拆分为n-…

Linux 网络传输学习笔记

这篇是混合《Linux性能优化实战》以及 《Wireshark网络分析就这么简单》的一些关于Linux 网络的学习概念和知识点笔记 &#xff0c;主要记录网络传输流程以及对于TCP和UDP传输的一些影响因素 Linux 网络传输流程 借用一张倪朋飞先生的《Linux性能优化实战》课程中的图片 接收流…

OVL assertion checker

目录 单周期断言&#xff1a; 1、ovl_always&#xff1a; 功能描述&#xff1a; ovl和assert_always例子&#xff1a;​编辑​编辑 详细介绍&#xff1a; 2、ovl_implication&#xff1a; 功能描述&#xff1a; 例子&#xff1a; 详细介绍&#xff1a; 3、ovl_never…

JDWP原理分析与漏洞利用

JDWP(Java DEbugger Wire Protocol):即Java调试线协议,是一个为Java调试而设计的通讯交互协议,它定义了调试器和被调试程序之间传递的信息的格式。说白了就是JVM或者类JVM的虚拟机都支持一种协议,通过该协议,Debugger 端可以和 target VM 通信,可以获取目标 VM的包括类…

Sphinx的原理详解和使用

一、Sphinx介绍 1.1 简介 Sphinx是由俄罗斯人Andrew Aksyonoff开发的一个全文检索引擎。意图为其他应用提供高速、低空间占用、高结果 相关度的全文搜索功能。Sphinx可以非常容易的与SQL数据库和脚本语言集成。当前系统内置MySQL和PostgreSQL 数据库数据源的支持&#xff0c;也…

455. 分发饼干 - 力扣(LeetCode)

题目描述 假设你是一位很棒的家长&#xff0c;想要给你的孩子们一些小饼干。但是&#xff0c;每个孩子最多只能给一块饼干。 对每个孩子 i&#xff0c;都有一个胃口值 g[i]&#xff0c;这是能让孩子们满足胃口的饼干的最小尺寸&#xff1b;并且每块饼干 j&#xff0c;都有一个尺…

作者推荐 | 【深入浅出MySQL】「底层原理」探秘缓冲池的核心奥秘,揭示终极洞察

探秘缓冲池的核心奥秘&#xff0c;揭示终极洞察 缓存池BufferPool机制MySQL缓冲池缓冲池缓冲池的问题 缓冲池的原理数据预读程序的局部性原则&#xff08;集中读写原理&#xff09;时间局部性空间局部性 innodb的数据页查询InnoDB的数据页InnoDB缓冲池缓存数据页InnoDB缓存数据…

区间dp/线性dp,HDU 4293 Groups

一、题目 1、题目描述 After the regional contest, all the ACMers are walking alone a very long avenue to the dining hall in groups. Groups can vary in size for kinds of reasons, which means, several players could walk together, forming a group.   As the …