数据结构:单调栈和单调队列

文章目录

  • 一、单调栈
    • 1.1、栈的思想
    • 1.2、单调栈
      • 1.2.1、单调栈的基本应用:找出数组中每个元素右侧第一个更大的元素
      • 1.2.2、单调栈的基本应用:找出数组中每个元素左侧第一个更大的元素
      • 1.2.3、单调栈拓展
      • 1.2.4、单调栈LeetCode题单
  • 二、单调队列
    • 2.1、队列的思想
    • 2.2、单调队列
      • 单调队列的应用:滑动窗口最大值
  • 三、单调栈和单调队列的区别
    • 示例解释

在学习单调队列或单调栈时,我们要先清楚,为何栈或队列是保持单增或单减,并且这样为何是有效的。比如保持单增,用单调队列的思想考虑的情况下,在遍历的过程中,我们需要解决的问题是寻找第一个比它小的(或者维护窗口中最小的元素),当前元素进队/栈时,如果栈顶或队尾存在比当前元素大的元素时,这些元素都是冗余的,因为当前元素在往后考虑时的作用 会一定更接近往后的元素且更小(更满足我们需要第一个小的要求)并且在单调队列中也更会留在窗口中。(单调栈有不同实现方式和思想,这里只描述了一种,详情请往下看)

一、单调栈

1.1、栈的思想

  栈是一种非常直观且广泛应用的数据结构,其主要特点是后进先出(LIFO,Last In, First Out)。想象一下一摞盘子或书籍,你只能从顶部添加或移除它们。栈可以临时存放一些数据,以便于之后逆序访问它,比如进制转换。
  浏览器的前后进是个很形象的例子:浏览器允许用户后退和前进浏览过的网页。这可以通过两个栈来实现:一个栈用于后退,另一个用于前进。当你访问新页面时,前进栈清空,当前页面压入后退栈。当你点击后退时,从后退栈中弹出,并将其压入前进栈。前进按钮则相反。

1.2、单调栈

  单调栈是一种特殊的栈,其元素按照单调递增或单调递减的顺序排列(根据特殊需求也可以是非减或非增序列)。单调栈用于解决那些需要寻找每个元素左侧或右侧第一个比它大(或小)的元素的问题。当新的元素被尝试加入栈时,会从栈顶开始移除破坏单调性的元素,直到保持栈的单调性为止,然后将新元素入栈。
应用示例:在一个数组中,为每个元素找出其右侧或左侧第一个更大的元素。LeetCode:柱状图中最大的矩形

如果要求的是左侧或右侧的最大/小值(而不是第一个更大/小的),可以用动态规划求解,如LeetCode:接雨水

1.2.1、单调栈的基本应用:找出数组中每个元素右侧第一个更大的元素

  使用单调栈解决这个问题的基本思路是遍历数组,对于每个元素,我们想找到它右侧第一个更大的元素。单调栈可以帮助我们追踪已经遍历过的元素,并保持它们的顺序,以便快速找到每个元素的答案。

  • 初始化一个空栈,用于存放数组元素的索引。
  • 遍历数组中的每个元素:
    • 当栈不为空且当前元素大于栈顶索引对应的元素时,表示找到了栈顶元素右侧的第一个更大元素。此时,将栈顶元素出栈,并记录当前元素为栈顶元素右侧第一个更大的元素。
    • 将当前元素的索引入栈。
  • 对于栈中剩余的元素,它们右侧没有更大的元素。
#include <vector>
#include <stack>
using namespace std;class Solution {
public:vector<int> nextGreaterElement(vector<int>& nums) {int n = nums.size();vector<int> ans(n, -1); // 初始化结果数组,假设每个元素的右侧没有更大的元素stack<int> myStack; // 用于存储索引,栈顶到栈底单调递减for (int i = 0; i < n; ++i) {// 当前元素大于栈顶元素对应的值时,说明找到了一个更大的元素while (!myStack.empty() && nums[i] > nums[myStack.top()]) {ans[myStack.top()] = nums[i]; // 更新栈顶元素的下一个更大元素myStack.pop(); // 弹出栈顶元素}// 将当前元素的索引入栈myStack.push(i);}// 对于栈中剩余的元素,它们的右侧没有更大的元素,ans中已经预设为-1,因此无需再操作return ans;}
};

1.2.2、单调栈的基本应用:找出数组中每个元素左侧第一个更大的元素

  可以直接使用1.2.1的方法反向扫描,反向扫描的右边实际上是原来的左边。如果在一个问题中同时求这俩,那用反向扫描肯定是最便捷的方式。 也可以直接从左往右扫描,如果栈顶元素比当前元素小则弹栈,直到遇到比当前元素大的则是左侧第一个更大元素。(这和单调队列的弹出队列的方式很像,因为比它小的不仅对以后没用,对当前元素来说也没用。)

#include <vector>
#include <stack>
using namespace std;class Solution {
public:vector<int> leftGreaterElement(vector<int>& nums) {int n = nums.size();vector<int> ans(n, -1); // 初始化结果数组,假设每个元素的左侧没有更大的元素stack<int> myStack; // 用于存储索引,栈顶到栈底单调递减for (int i = 0; i < n; ++i) {// 当前元素大于栈顶元素对应的值时,说明当前元素是遍历到目前为止的最大元素// 这里不需要像找右侧元素那样进行元素的更新,因为我们关心的是左侧元素while (!myStack.empty() && nums[i] >= nums[myStack.top()]) {myStack.pop(); // 弹出栈顶元素}// 如果栈不为空,说明找到了当前元素左侧的第一个更大元素if (!myStack.empty()) {ans[i] = nums[myStack.top()];}// 将当前元素的索引入栈myStack.push(i);}return ans;}
};

1.2.3、单调栈拓展

  单调栈的一次遍历不仅仅只能解决找到第一个更小的问题,它一次遍历就能找到左右两边的信息,不过有一边是等高的,有时候我们可以利用这一个特点来处理问题。这样的拓展使用需要在不同问题中发现,如1.2.4列出的题单。

for (int i = 0; i < n; ++i) {//递减序while (!myStack.empty() && nums[i] >= nums[myStack.top()]) {//右侧if(nums[i]>nums[myStack.top()]) 则能找到右侧更大,但可能出现相等的情况,可能相等的情况并不影响答案//所以需要有这种考虑和想法,以便于后面遇到这样的问题能够思考到,然后利用起来myStack.pop(); // 弹出栈顶元素}if (!myStack.empty()) {left_max[i] = nums[myStack.top()];//左边更大一定是正确的}myStack.push(i);
}

1.2.4、单调栈LeetCode题单

在这里插入图片描述

二、单调队列

2.1、队列的思想

  队列是一种先进先出(First In, First Out,FIFO)的数据结构,其工作原理类似于日常生活中的排队等待。在队列中,元素从一端(通常称为队尾)添加,从另一端(称为队头)进行移除。这种结构确保了元素被处理的顺序正是它们被添加到队列中的顺序,就像人们在商店结账处排队一样:先来的人先得到服务,新来的人排在队伍的末尾。

2.2、单调队列

  单调队列是一种特殊的队列,其元素同样按照单调递增或单调递减的顺序排列。不同于单调栈,单调队列支持在两端进行操作:在队列的一端添加元素,在另一端移除元素。这种结构适用于滑动窗口类的问题,其中窗口在数据序列上滑动,而我们希望快速获取窗口内的最大值或最小值。
应用示例:给定一个数组和一个窗口大小,为每个窗口找出最大值或最小值。

单调队列的应用:滑动窗口最大值

  单调队列解决的是另一个问题:给定一个数组和一个窗口大小,为每个窗口找出最大值。单调队列通过维护一个双端队列(Deque),其中保存可能成为当前窗口最大值的元素索引,确保队列是单调递减的。LeetCode求滑动窗口最大值

  • 初始化一个空的双端队列(Deque)。
  • 遍历数组中的每个元素:
    • 移除队列中所有小于当前元素的索引,因为它们不可能是包含当前元素的窗口的最大值。
    • 检查队头索引是否已经滑出窗口(即队头索引对应的元素不在当前考虑的窗口内),如果是,将其从队头移除。
    • 将当前元素的索引添加到队列尾部。
    • 对于每个窗口,队头索引总是对应该窗口的最大值。且队列里总是会有元素(因为至少当前正在遍历的元素一定在窗口中)。
#include <vector>
#include <deque>
using namespace std;class Solution {
public:vector<int> maxSlidingWindow(vector<int>& nums, int k) {deque<int> myque; // 存储的是nums的索引,保证从大到小排列vector<int> ans;for(int i = 0; i < nums.size(); ++i) {// 如果队列不为空且当前元素大于等于队列最后一个元素所对应的值,则弹出队列最后一个元素while(!myque.empty() && nums[i] >= nums[myque.back()]) {myque.pop_back();}// 将当前元素索引加入队列myque.push_back(i);// 确保队列第一个元素始终在当前滑动窗口的范围内if(myque.front() <= i - k) {myque.pop_front();}// 当索引达到窗口大小-1时,开始记录结果if(i >= k - 1) {ans.push_back(nums[myque.front()]);}}return ans;}
};

三、单调栈和单调队列的区别

  你会发现单调队列和单调栈的区别在于,是否包含一个滑动窗口,单调队列处理的之前的成员可能会"失效",但是单调栈的成员一直不会失效,因此单调队列有一个“失效”出队的操作。单调栈处理的问题中,一旦元素入栈,它们就保持有效,直到被明确地由一个满足特定条件的后来者替代;而单调队列处理的问题中,元素的有效性不仅受到队列中其他元素的影响,还受到它们是否仍然处于考虑的窗口内的影响。

示例解释

  假设你有一系列人的身高,你需要找到每个人右侧的第一个更高的人(单调栈),或者在一系列长度为k的连续子序列(即窗口)中找到最高的人(单调队列)。

  • 单调栈:当一个新人加入时,如果他比前面的人都高,那么他就成为了前面某些人右侧第一个更高的人。前面比他矮的人都不再重要,因为他们已经找到了比自己高的人。
  • 单调队列:对于每个长度为k的窗口,你想快速知道最高的人。当一个新人加入窗口时,如果他比窗口中的某些人高,那么这些比他矮的人就不可能是该窗口的最高者了。但是,窗口滑动时,最高的人可能会离开窗口,所以你需要记录下一个可能最高的人。

  通过使用单调栈和单调队列,你可以高效地解决这些问题,而不需要对每个元素或每个窗口进行独立的比较。每个元素进栈(队)一次,出栈(队)一次,因此时间复杂度均为O(n)

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

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

相关文章

java数据结构与算法刷题-----LeetCode34. 在排序数组中查找元素的第一个和最后一个位置

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 二分查找 二分查找 解题思路&#xff1a;时间复杂度O( l o g 2 …

算法沉淀——拓扑排序

前言&#xff1a; 首先我们需要知道什么是拓扑排序&#xff1f; 在正式讲解拓扑排序这个算法之前&#xff0c;我们需要了解一些前置知识&#xff08;和离散数学相关&#xff09; 1、有向无环图&#xff1a; 指的是一个无回路的有向图。 入度&#xff1a;有向图中某点作为图…

HarmonyOS 应用开发之启动/停止本地PageAbility

启动本地PageAbility PageAbility相关的能力通过featureAbility提供&#xff0c;启动本地Ability通过featureAbility中的startAbility接口实现。 表1 featureAbility接口说明 接口名接口描述startAbility(parameter: StartAbilityParameter)启动Ability。startAbilityForRes…

人工智能|推荐系统——搜索引擎广告

原文题目 Dark sides of artificial intelligence: The dangers of automated decision-making in search engine advertising(JASIST,2023) 人工智能的阴暗面:搜索引擎广告自动决策的危险 摘要 随着人工智能应用的日益广泛,搜索引擎供应商越来越多地要求广告商使用基于机…

k8s局域网通过operator部署rabbitmq

参考&#xff1a;Installing RabbitMQ Cluster Operator in a Kubernetes Cluster | RabbitMQ 1、下载cluster-operator.yml wget https://github.com/rabbitmq/cluster-operator/releases/download/v2.7.0/cluster-operator.yml 2、拉取对应的镜像&#xff0c;这里的版本是根…

什么是搜索引擎(SEO)爬虫它们是如何工作的?

什么是搜索引擎&#xff08;SEO&#xff09;爬虫&它们是如何工作的&#xff1f; 你的网站上有蜘蛛&#x1f577;️。别抓狂&#xff01;我说的不是真正的八条腿的蜘蛛&#x1f577;️。 我指的是搜索引擎优化爬虫。他们是实现SEO的机器人。每个主要的搜索引擎都使用爬虫来…

linux centos7.9 weblogic14c java1.8.401 安装部署流程

一、获取安装包&#xff1a; Java1.8.401&#xff1a;Java Downloads | Oracle weblogic 14c&#xff1a;https://download.oracle.com/otn/nt/middleware/14c/14110/fmw_14.1.1.0.0_wls_lite_Disk1_1of1.zip 选generic版本 二、将安装包传到Linux服务器上 方法不限&#xf…

物理寻址和功能寻址,服务器不同的应答策略和NRC回复策略

1&#xff1a;功能寻址&#xff0c;服务器应答与NRC回复策略 详细策略上&#xff0c;又分为服务有子功能&#xff0c;和不存在子功能。 1.1功能寻址&#xff0c;存在子功能 存在子功能的情况下&#xff0c;又分为supress postive response &#xff08;即子功能字节的bit7&a…

Servlet基础 管理员注册页面

管理员注册页面 index.jsp <% page language"java" import"java.util.*" pageEncoding"UTF-8"%> <% String path request.getContextPath(); String basePath request.getScheme()"://"request.getServerName()":&quo…

搜索与图论——bellman—ford算法、spfa算法求最短路

bellman-ford算法 时间复杂度O(nm) 在一般情况下&#xff0c;spfa算法都优于bf算法&#xff0c;但遇到最短路的边数有限制的题时&#xff0c;只能用bf算法 bf算法和dijkstra很像 #include<iostream> #include<queue> #include<cstring> #include<algori…

新数字时代的启示:揭开Web3的秘密之路

在当今数字时代&#xff0c;随着区块链技术的不断发展&#xff0c;Web3作为下一代互联网的概念正逐渐引起人们的关注和探索。本文将深入探讨新数字时代的启示&#xff0c;揭开Web3的神秘之路&#xff0c;并探讨其在未来的发展前景。 1. Web3的定义与特点 Web3是对互联网未来发…

安装docker 并搭建出一颗爱心树

1、docker介绍 Docker 是⼀个开源的容器运⾏时软件&#xff08;容器运⾏时是负责运⾏容器的软件&#xff09;&#xff0c;基于 Go 语 ⾔编写&#xff0c;并遵从 Apache2.0 协议开源。 Docker可以让开发者打包⾃⼰的应⽤以及依赖到⼀个轻量的容器中&#xff0c;然后发布到任何…

如何通过针对iOS的动态分析技术绕过反调试机制

在这篇文章中&#xff0c;我们将跟大家介绍和分析一种针对iOS的新型安全研究技术&#xff0c;该技术能够让iOS应用程序的调试过程更加轻松&#xff0c;并解决那些可能会延缓我们步伐的阻碍。 如果你要对一个采用了反调试技术的iOS应用程序或二进制文件进行调试的话&#xff0c;…

民航电子数据库:CAEMigrator迁移数据库时总是卡死

目录 一、场景二、异常情况三、排查四、应急方案 一、场景 1、对接民航电子数据库 2、将mysql数据库迁移到cae数据库 3、使用CAEMigrator迁移工具进行数据库迁移时&#xff0c;该工具会卡死&#xff08;不清楚是否是部署cae服务的服务器资源导致&#xff09; 二、异常情况 …

【python plotly库介绍】从视觉到洞见:桑基图在业务分析中的应用【保姆级教程过于详细珍藏版】

&#x1f464;作者介绍&#xff1a;10年大厂数据\经营分析经验&#xff0c;现任大厂数据部门负责人。 本文结构&#xff1a;工具介绍python实现库 - 案例 - 分析思路与过程 实战专栏&#xff1a;https://blog.csdn.net/cciehl/category_12615648.html 备注说明&#xff1a; 企业…

2024软件设计师备考讲义——(7)

数据库技术 一、数据库基础 1.数据库系统 DB、DBS、DBA、DBMS 2.三级模式两级映像 内模式 物理存储 概念模式 基本表 外模式 用户使用应用程序&#xff0c;视图级别 外模式-概念模式的映像 表和视图之间的映射若表中数据变化&#xff0c;只要修改映射&#xff0c;不用改程序…

DARTS-PT: RETHINKING ARCHITECTURE SELECTION IN DIFFERENTIABLE NAS

Rethinking Architecture Selection in Differentiable NAS 论文链接&#xff1a;https://arxiv.org/abs/2108.04392v1 项目链接&#xff1a;https://github.com/ruocwang/darts-pt ABSTRACT 可微架构搜索(Differentiable Neural Architecture Search, NAS)是目前最流行的网…

光耦合器电路基本概述

在当今快速发展的技术环境中&#xff0c;光耦合器电路在确保电信号跨隔离电路的无缝传输方面发挥着关键作用。这些半导体器件通常被称为光电隔离器&#xff0c;具有许多优点&#xff0c;包括电路隔离、信号传输和精确的电路控制。然而&#xff0c;如果不全面了解市场上各种光耦…

Verilog语法之always语句学习

always语法是Verilog_HDL中最常用的一种语法。 always过程语句和语句块组成的&#xff0c;语法格式如下所示。 always(敏感信号1 or 敏感信号2.....) always实现组合逻辑和时序逻辑。用always实现组合逻辑要将所有的敏感信号加入敏感列表中&#xff1b;用always实现时序逻辑时…

vue 窗口内容滚动到底部

onMounted(() > {scrollToBottom() }) // 滚动到底部方法 const scrollToBottom () > {// 获取聊天窗口容器let chatRoom: any document.querySelector(".chat-content");// 滚动到容器底部chatRoom.scrollTop chatRoom.scrollHeight; } 效果 聊天窗口代码…