总结汇总一下栈与队列题型。
栈与队列的核心理解
什么是栈
栈是一种 后进先出(LIFO, Last In First Out)的数据结构。最后放进去的元素,最先被拿出来。
最常见的几个动作是:
push:压栈pop:弹栈top / peek:查看栈顶
栈的直觉非常像一摞盘子。你只能从最上面继续放,也只能从最上面拿。
刷题里,栈最常用于解决下面几类问题:
- 配对与匹配,比如括号匹配
- 撤销、回退、消消乐
- 表达式求值
- 单调栈维护"下一个更大/更小元素"
什么是队列
队列是一种 先进先出(FIFO, First In First Out)的数据结构。最先进入队列的元素,会最先离开。
常见动作是:
push / offer:入队pop / poll:出队front / peek:查看队头
队列非常像排队买饭。先来的人先处理,后来的只能排在后面。
刷题里,队列最常用于:
- 按顺序处理元素
- 层序遍历
- BFS 最短步数问题
- 滑动窗口
- 单调队列维护窗口最值
栈和队列的区别
最核心的区别其实只有一句话:
- 栈:后进先出
- 队列:先进先出
也正因为这个区别,它们擅长处理的问题也不同:
- 栈更擅长"最近的、嵌套的、需要回退的"关系
- 队列更擅长"按到达顺序逐个处理"的关系
所以很多题目不是在考你会不会写 append 和 pop,而是在考你:
这个问题需要的是最近进入的元素,还是最早进入的元素?什么情况下想到栈
看到下面这些特征,优先想栈:
- 括号匹配、标签匹配、路径回退
- 撤销、恢复、消消乐、相邻抵消
- 表达式求值
- 需要维护"最近一个比当前大/小"的元素
- 需要在扫描过程中保存"还没处理完的历史信息"
一句话记忆:
只要题目和“最近的未完成状态”有关,就很容易想到栈。什么情况下想到队列
看到下面这些味道,优先想队列:
- 层序遍历
- 按轮次推进
- 最少步数、最短操作次数
- 数据流按顺序进入,旧元素会自然淘汰
- 滑动窗口
一句话记忆:
只要题目强调“顺序推进、一层一层扩散、最先进入最先处理”,就很容易想到队列。Python 中的栈、队列、双端队列、优先队列
Python 里最常用的几个容器如下:
- 普通栈:直接用
list - 普通队列 / 双端队列:用
collections.deque - 优先队列:用
heapq
其中:
list.append()+list.pop()就可以当栈用deque.append()/deque.popleft()很适合写队列heapq默认是小根堆,如果想模拟大根堆,通常存相反数
刷题时其实不用纠结"我是不是在手写一个正式数据结构",更重要的是:
我现在需要维护哪种顺序?基础栈题:后进先出
括号匹配
LC20 - 有效的括号
这是最基础的栈应用题,我们只需要用一个栈来决定就行。首先用字典记录右括号对应的左括号,右括号遇到左括号必须弹出,不然就是无效的,最后栈非空也是无效。
Python3
点击展开代码
展开代码
如果只有一种括号,那么有另外一种思路,用左括号数量去判断有效性。如果要让字符串有效,那么必须每个右括号 ) 的左边必须有一个左括号 ( 和它匹配。所以我们维持一个变量left,遇到左括号+,遇到右括号-,这样中间是否会右括号太多以及最后只要判断是不是正好抵消就行。(其实与栈思路一样,只不过只用了一个变量)。本题不行,因为有三种括号,所以要加大统计量。
LC921 - 使括号有效的最少添加
只有满足下面几点之一,括号字符串才是有效的:
-
它是一个空字符串,或者
-
它可以被写成 AB (A 与 B 连接), 其中 A 和 B 都是有效字符串,或者
-
它可以被写作 (A),其中 A 是有效字符串。 给定一个括号字符串 s ,在每一次操作中,你都可以在字符串的任何位置插入一个括号
-
例如,如果 s = "()))" ,你可以插入一个开始括号为 "(()))" 或结束括号为 "())))" 。 返回 为使结果字符串 s 有效而必须添加的最少括号数。
这一题可以回到需求的解法了,我们维持插入次数res、右括号的需求量need。如果是左括号,need+1即可;如果是右括号,我们不仅要-1,还要判断-1是不是变成刚需左括号了,如果是这样必须立刻补左括号,res+1。这样,我们最终返回res+need就是需要插入的总次数。
总而言之,我们在插入过程中维持有效性。
Python3
点击展开代码
展开代码
LC1541 - 平衡括号字符串的最少插入次数
本题和上一题的区别在于,一个left要对应两个右括号了,简单改一下就能出答案。
Python3
点击展开代码
展开代码
LC1249 - 移除无效的括号
这题要求从一个字符串中删除最小数量的括号,让括号有效。字符串中有一些无关小写字母。按照LC20相同的代码,其实我们就可以直接得到需要删除的个数,但是本题要求的是删除后的字符串,需要更多信息。但是其实改动也不多。
Python3
点击展开代码
展开代码
这一题,当然也可以用栈存待会要删除的下标(只存还没匹配的 ( 的下标),来之后构造答案,思路很自然:
Python3
点击展开代码
展开代码
综上,结构匹配型我们就用栈来做,数量欠账类的用res/need,构造答案则是两者皆可,用栈来存储更优雅。
表达式与字符串处理
LC150 - 逆波兰表达式求值
逆波兰式式经典应用栈求数值的题目。
Python3
点击展开代码
展开代码
LC71 - 简化路径
我们通过curr_name来判断目前两个/直接夹的东西,并采用对应的栈操作。
Python3
点击展开代码
展开代码
LC227 - 基本计算器 II
这一题,其实是中缀表达式求值,上一题的逆波兰表达式则是后缀表达式。后缀表达式可以轻松用栈解决,中缀则是可以通过栈来转为后缀。思路如下:
- 遇到操作数直接加入后缀表达式
- 遇到左括号直接入栈,然后遇到右括号依次弹出栈内运算符并加入后缀表达式,直到弹出 ( 为止。
- 遇到运算符,则比较栈顶的运算符的优先级,如果比栈顶优先级高/栈顶为 ( /栈为空,则直接加入表达式;否则将栈顶弹出加入表达式,然后重新判断。
- 重复直到遍历完毕,将栈所有元素弹出,加入后缀表达式
不过对于本题来说,并不存在括号,我们可以这样来处理:遇到加减立刻将数字(取反)压栈,遇到乘除则立刻和栈顶计算:
Python3
点击展开代码
展开代码
为什么可以这么做?这是因为,我们用栈调整了运算顺序。如果是优先级较低的+-,我们先记账,留到最后一起加减;如果是乘除,我们就立刻运算。
LC224 - 基本计算器
这一题属于有括号,但只有+-。因此,也是有简化的方式:我们不用处理优先级,只需要看看这一层算到了多少、这一层整体前面带的符号是什么。
- 用res记录当前层结果
- sign记录当前数字前的符号
- num组装多位数
- 遇到 ( 将外层res和sign压栈,进入新的一层
- 遇到 ) 先结算当前层,再和外层合并
Python3
点击展开代码
展开代码
LC772 - 基本计算器 III
这一题,不仅有括号,且有+-*/,没有什么比较好的方法遍历一次解决了。一般而言,我们可以用栈按照前面的技巧转为后缀表达式,然后用栈计算答案。但是,这一题更好的写法是"递归+栈",也就是每一层递归只处理一层的表达式,遇到 ( 就递归进去,拿子表达式的结果当成一个普通数字 num 来继续算。
所以其实这一题可以根据 基本计算器 II 加一个递归入口简单改编出来。
Python3
点击展开代码
展开代码
辅助栈与模拟栈
LC155 - 最小栈
最小栈是一种支持push、pop、top操作,并能在常数时间内检索到最小元素的栈,一般的解决思路是用两个栈来实现,一个栈stack来保存元素,然后用另一个栈min_stack[i] 表示主栈 stack[0..i] 这一层时的最小值:
Python3
点击展开代码
展开代码
LC232 - 用栈实现队列
又是数据设计题,我们可以用两个栈来模拟队列,其中一个是常规栈,另一个用于出栈倒腾顺序:
Python3
点击展开代码
展开代码
LC225 - 用队列实现栈
用两个队列来实现栈,思路如下:
- push(x):直接进主队列
- pop():把前 n-1 个元素转移到辅助队列,最后剩下的那个就是栈顶,弹出它
- 操作结束后交换 q1 和 q2,让 q1 继续做主队列
Python3
点击展开代码
展开代码
基础队列题:先进先出
普通队列
LC933 - 最近的请求次数
本题只记录和目前时间差3000以内的数目,所以我们可以直接用一个队列维护,如果队头和入队元素差距超过3000,直接弹出:
Python3
点击展开代码
展开代码
LC649 - Dota2 参议院
本题是一个轮流禁言题,也就是循环回合 + 先手淘汰。按照原始顺序流动,每个 R 可以禁言一个 D,每个 D 可以禁言一个 R,直到最后只剩同一阵容胜利。
这一题最好的解法是用两个队列存储还活着的议员下标,然后每次队头下标小的先行动,把对方队头ban掉,然后把自己的下标加上n放到队尾(等下一轮),被ban掉的出队就不会再回来了。
Python3
点击展开代码
展开代码
双端队列
LC641 - 设计循环双端队列
这一题在 408 里也很常见。我们需要维持一个 front 和一个 rear,把数组想象成一个环,然后对这个环做入队、出队和取模移动。
Python3
点击展开代码
展开代码
层序遍历与 BFS
队列常被用来实现层序遍历,所以这一组题也很适合放在这里一起理解。
LC102 - 二叉树的层序遍历
经典层序遍历,需要达到肌肉级别记忆。
Python3
点击展开代码
展开代码
LC107 - 二叉树的层序遍历 II
自底向上的层序遍历,和上一题相比只是 ans 改成从左边添加而已。
Python3
点击展开代码
展开代码
LC103 - 二叉树的锯齿形层序遍历
我们可以根据ans的长度得到奇数层偶数层,从而决定这个level是append还是appendleft。
Python3
点击展开代码
展开代码
LC199 - 二叉树的右视图
右视图其实就是每层的最后一个节点,所以在普通层序遍历的基础上,取每个 level[-1] 即可。这题和前面几题的差别不在数据结构,而在"每层要收集哪一个元素"。
图论中的队列
LC994 - 腐烂的橘子
本题用队列来存储腐烂橘子的坐标,弹出的时候让周围的新鲜橘子入队,每层扩散一次,时间 +1。当队列为空时遍历结束,这时候如果还有新鲜橘子就不可能全部腐烂,返回 -1,否则返回分钟数。
Python3
点击展开代码
展开代码
LC752 - 打开转盘锁
这一题的目标是最少步数,所以应该用BFS来做,因为BFS是天然的求"无权路最短图"的方法。如果用DFS,会一路走到底,然后要保证路径最短,还要将所有可能路径搜完,再取最小值,复杂度很差,且存在大量重复状态。
我们用bfs的次数就可以统计进行的步数,所以这一题只要把握好状态更新入队即可。
Python3
点击展开代码
展开代码
LC773 - 滑动谜题
依旧状态更新,依旧最短次数。我们直接一个广搜,用队列存储更新状态。可是,如果每次队列都存储一个二维数组,负担太大了,而且由于我们已经知道是2x3的板子了,可以先预处理好可以相邻下标。
Python3
点击展开代码
展开代码
单调栈:维护下一个更大或更小
单调栈的核心思想
单调栈本质上还是栈,只不过我们在压栈时额外维护了一个"单调性":
- 要么从栈底到栈顶单调递增
- 要么从栈底到栈顶单调递减
它最擅长解决的问题是:
- 下一个更大元素
- 下一个更小元素
- 上一个更大 / 更小元素
- 左右第一个打破单调性的位置
- 区间贡献问题
很多人第一次学单调栈,会觉得它像魔法。其实它做的事情非常朴素:
新元素一来,就把那些已经不可能成为答案的旧元素弹掉。所以单调栈的关键不在"背模板",而在想清楚:
- 栈里到底存的是值还是下标
- 当前元素来了之后,谁已经失去资格了
- 弹栈的那一刻,我能不能顺便结算答案
下一个更大元素
LC496 - 下一个更大元素 I
要找下一个更大元素,单调栈就是非常自然的解法。我们可以用哈希表记录每个元素下一个更大元素。
Python3
点击展开代码
展开代码
LC503 - 下一个更大元素 II
这一题要求 nums 中每个元素的下一个更大元素,但是本题数组是循环数组。这一题的解法实际上只要做两个处理:
- 让循环范围扩大确保每个元素都判断一次是否是下一个元素
- 然后让下标定位时取余即可
Python3
点击展开代码
展开代码
LC739 - 每日温度
一道很规整的单调栈题目,存下标,算距离存入。
Python3
点击展开代码
展开代码
找左右边界
LC84 - 柱状图中最大的矩形
本质上,也是在寻找左右两侧第一个比当前矩形矮的,然后这时候就可以得出当前矩形能贡献出来的最大矩形。
Python3
点击展开代码
展开代码
LC85 - 最大矩形
这一题,实际上是枚举每一行作为底边,来做多个一维柱状图最大矩形问题。
Python3
点击展开代码
展开代码
LC901 - 股票价格跨度
设计一个算法收集某些股票的每日报价,并返回该股票当日价格的 跨度 。当日股票价格的 跨度 被定义为股票价格小于或等于今天价格的最大连续日数(从今天开始往回数,包括今天)。
这一题和普通"找下一个更大元素"的题有一点不一样。它不是一次性给完整数组,而是数据流在线输入,所以不能等全部输入完再统一求。
思路是维护一个单调递减栈,栈里存 (price, span)。如果当前价格比栈顶大,那么栈顶那一整段跨度都能被当前价格吞掉,于是我们把它们的跨度累加起来。这就是这一题看起来"多了一个累加量"的原因。
Python3
点击展开代码
展开代码
单调栈变形
LC853 - 车队
这一题属于"排序 + 单调栈"的变形。把车按位置从左到右排序后,每辆车都有一个到终点的时间 time = (target - pos) / speed。如果后车到终点所需时间小于等于前车,那么它最终一定会追上前车,合并成同一个车队。
所以我们只需要维护一个单调的到达时间栈,能合并的就弹掉,最后栈里剩下的就是车队数量。
Python3
点击展开代码
展开代码
区间贡献问题
LC907 - 子数组的最小值之和
这一题如果直接暴力二重循环弄出所有子数组,会超时。所以必须要转化思路,核心思想是算每个 arr[i] 作为最小值时,能贡献多少个子数组。
实际上,我们依旧要回到单调栈的原本思想,我们要看 arr[i] 左边第一个比它小的位置、右边第一个小于等于它的位置在哪。left[i] = 从 i 往左,最多能选多少个起点;right[i] = 从 i 往右,最多能选多少个终点;那么,arr[i] 的贡献 = arr[i] * left[i] * right[i]。
比如,[3,1,2,4],3左边没有更小的,所以左侧可选一个起点;右侧第一个小于等于它的是1,所以只能是[3]。而1左侧没有比它更小的,起点可以选3或者1,右侧是没有小于等于它的,可以选1、2、4,一共贡献 123 = 6。
Python3
点击展开代码
展开代码
LC2104 - 子数组范围和
返回所有连续子数组最小元素和最大元素差值,在上一题中,我们已经求过了最小值贡献,改一下不等于号方向就可以求最大值贡献了。然后,用最大值贡献-最小值贡献,就是本题的答案。
Python3
点击展开代码
展开代码
本题其实可以细细品味,包含着单调栈的核心思想。
单调栈模板
最常见的三个模板其实可以这样记:
Python3
点击展开代码
展开代码
Python3
点击展开代码
展开代码
Python3
点击展开代码
展开代码
单调队列:维护滑动窗口最值
单调队列的核心思想
单调队列本质上是 deque + 维护单调性。
和单调栈相比,它最大的不同在于:单调队列往往服务于滑动窗口。所以除了维护单调性,还要解决一个额外问题:
队头这个元素过期了吗?于是单调队列的经典操作一般有两步:
- 新元素进来时,从队尾弹出所有"不如它"的元素
- 窗口左端移动时,判断队头是否已经滑出窗口
所以单调队列最擅长的就是:
- 滑动窗口最大值 / 最小值
- 窗口合法性判定
- 前缀和 + 最优左端点筛选
滑动窗口最值
LC239 - 滑动窗口最大值
这是单调队列最经典的使用题。我们用deque,保持队头始终是当前窗口的最大值,然后每次验证队头是否过期即可。
Python3
点击展开代码
展开代码
前缀和 + 单调队列
LC862 - 和至少为 K 的最短子数组
给你一个整数数组 nums 和一个整数 k ,找出 nums 中和至少为 k 的 最短非空子数组 ,并返回该子数组的长度。如果不存在这样的 子数组 ,返回 -1 。(子数组连续)
我们知道,要想求任意子数组的和,用前缀和就可以解决。这题不能使用滑动窗口,因为 LC862 里可能有负数。有负数时,窗口右扩后,和不一定变大;左缩后,和也不一定变小,所以普通双指针失效。
构造好前缀和之后,用单调队列维护一批最有希望成为左端点的前缀和下标。为了让和尽量大,我们让最小的前缀和始终保持在队顶,然后遍历的时候每次和当前 pre[i] 做差得到和,大于k之后,将ans更新为较小的坐标长度。
Python3
点击展开代码
展开代码
这一题代码虽然很短,但思路转化并不直观,所以一定要把"前缀和 + 单调队列"的套路记住。
LC1438 - 绝对差不超过限制的最长连续子数组
依旧是滑动窗口最值,这次既要最大又要最小,那么好,用两个队列就可以满足。
这一题包含比较通用的滑动窗口最值求解,请仔细阅读:
Python3
点击展开代码
展开代码
单调队列模板
单调队列最常见的模板其实就是下面这版:
Python3
点击展开代码
展开代码
如果同时需要维护最大值和最小值,就开两个队列:
- 一个单调递减,维护最大值
- 一个单调递增,维护最小值
而如果题目是 LC862 这种"前缀和 + 最短子数组",那么队列里存的就不是原数组元素,而是前缀和下标。这一点要单独记。
优先队列:动态维护最值
优先队列的核心思想
优先队列的题,核心味道其实非常明显:
候选集会动态变化,但每一步只关心其中的一个最值。这时就很容易想到堆。
常见信号包括:
- 一边扫描,一边动态加入候选
- 每次只需要当前最大 / 当前最小
- Top K
- 多路归并
- 数据流维护第 k 大 / 中位数
在 Python 里,刷题时通常直接用 heapq:
- 默认是小根堆
- 想要大根堆,就存相反数
Top K 问题
LC215 - 数组中的第 K 个最大元素
Top K问题,可以通过快速选择算法来解决。我们这里先复习一下优化重复元素性能的三路快选。
Python3
点击展开代码
展开代码
然后,我们就进入这一题的优先队列写法。一般优先队列题优先用heapq即可,堆就是优先队列的常见实现方式。我们通过取反模拟大根堆,然后弹出k-1个之后得到第k个最大元素。
Python3
点击展开代码
展开代码
另一种做法是固定大小的小根堆,这样会一直保留最小的k个,如果新元素比堆顶大,就换堆顶,到最后堆顶就会变成最大的k个中的最小的,也就是第k个最大元素。
Python3
点击展开代码
展开代码
LC347 - 前 K 个高频元素
这一题最符合直觉的做法是哈希表+小根堆,我们维持大小为k的小根堆,遇到大的就替换掉堆顶,最终就会剩余前k个高频元素。
Python3
点击展开代码
展开代码
LC703 - 数据流中的第 K 大元素
Python3
点击展开代码
展开代码
合并多个有序结构
LC23 - 合并 K 个升序链表
之前合并k个升序链表是通过分治归并法解决,这里可以使用堆来解决这个问题。我们将所有链表当前节点放在最小堆里,每次取出堆顶构造。
Python3
点击展开代码
展开代码
需要注意的是,我们必须要防止 Python 直接比较 node,因为链表节点本身不能比较大小。所以我们要存 node.val 和 node 的元组。但是题目中 node.val 还是可能会相等,然后顺位又去比较 node,所以我们还要在中间塞一个 idx,来保证永远比不到 node。
LC373 - 查找和最小的 K 对数字
最简单的写法就是二重循环全部入大小k的大根堆,每次超限踢出最大的,剩下的就是最小的K个。
Python3
点击展开代码
展开代码
但是,这么写显然丢失了题目中的重要信息,即有序。假设nums1和nums2都有三个,我们可以写出3x3的矩阵,每一行都可以看成一个有序链表,比如1、7、11;2、4、6:
2 4 61 -> 3 5 77 -> 9 11 1311 -> 13 15 17显然,我们就可以套用 LC23 的思路,先把每一行第一个数字入堆,然后弹出当前最小对,再继续看这一行的下一个,直到取满 k 个。
Python3
点击展开代码
展开代码
数据流与对顶堆
LC295 - 数据流的中位数
注意,没说输入的数据是按顺序的,虽然样例是按顺序的。这一题的解法是用两个堆,天然拿出左半边最大的和右半边做小的,注意做好奇偶处理。
Python3
点击展开代码
展开代码
贪心 + 优先队列
有时候,我们需要一边扫描,一边动态加入候选,并且反复取当前最大/最小。
LC253 - 会议室 II
堆维护"现在占着资源的人"
给一个安排时间的数组,每个会议时间包含开始和结束,返回所需会议室最小数量,也就是: [[0,30],[5,10],[15,20]] -> 2。
这一题,是通过最小堆+优先队列实现的,堆里的元素表示要占用到哪个时间点。这样,新元素如果大于最小堆的最早结束时间,说明可以重复使用这个会议室,我们移除堆顶然后入堆即可:
Python3
点击展开代码
展开代码
LC871 - 最低加油次数
堆维护"过去留下来的备选资源"
这一题的标准思路是,我们维持一个大根堆,用fuel表示最远能到哪里,按顺序扫描所有加油站,把所有"已经能到达的站"的油量放入一个大根堆。如果当前的油不够去下一站/终点,就从大根堆里面拿一个最大的油补上,每弹一次堆,表示加油一次。
所以,这一题实际上是贪心用最大油量加,从而让加油量尽量少。为了实现这个贪心借助了堆结构。
Python3
点击展开代码
展开代码
Python heapq 使用要点
这里把 Python 里最常用的几个点单独记一下:
Python3
点击展开代码
展开代码
几个高频注意点:
heapq默认是小根堆heapq.heapify(nums)是原地建堆,返回值是None- 想模拟大根堆,通常存相反数
- 处理链表节点、对象、元组时,要注意比较规则
- 如果元组前几位可能相等,后面又是不可比较对象,需要补一个
idx
一句话记忆:
Python 里优先队列几乎就是 list + heapq。栈与队列设计题
栈的设计题
LC155 - 最小栈
核心是"普通栈存值 + 辅助栈同步最小值"。难点不在 API,而在于要不要让 min_stack 和主栈等长。本文前面用的是最稳妥的等长写法。
LC225 - 用队列实现栈
核心是让队列模拟"最后进来的先出去"。最稳定的写法是双队列倒腾,把最后一个元素单独留下来当栈顶。
设计题的核心不是套模板,而是想清楚"我要额外维护什么信息"。
这两题分别对应:
LC155:普通栈 + 同步最小值栈LC225:用两个队列模拟"最后进入的先出来"
队列的设计题
LC232 - 用栈实现队列
核心是让一个栈负责输入、另一个栈负责输出。只有在输出栈为空时,才把输入栈整体倒过去。
LC622 - 设计循环队列
核心是循环数组 + 取模。建议统一采用"浪费一个位置"的写法,这样空和满的判断最清楚。
LC641 - 设计循环双端队列
比 LC622 多的只是"两头都能操作",本质仍然是循环数组和指针含义的统一。
队列设计题常见就三类:
- 普通队列:先进先出
- 循环队列:用取模节省空间
- 双端队列:两头都能插入和删除
这类题一定要先把指针含义定死,不然特别容易写着写着把自己绕晕。最推荐的定义方式是:
front指向队头元素rear指向队尾后一个空位- 空队列:
front == rear - 满队列:浪费一个位置,判断
(rear + 1) % capacity == front
栈与队列题目的分类判断
一看到括号匹配、消消乐、撤销操作,就想栈
因为这类题都在维护"最近一个还没处理完的状态"。括号要和最近的左括号配对,消消乐要和最近的字符比,撤销也总是撤销最近一步。
一看到层序遍历、按顺序处理、最短步数,就想队列
因为队列天然按顺序推进,而 BFS 又天然按层扩散。凡是"从起点最少几步到终点""一轮一轮蔓延"的题目,优先就该想到队列。
一看到下一个更大元素、找左右边界,就想单调栈
这类题本质都在找"第一个打破单调性的位置"。只要当前元素一来,前面一批元素的答案就能被结算,这就是单调栈最典型的信号。
一看到滑动窗口最大值、窗口最小值,就想单调队列
因为窗口在移动,而我们又需要实时拿到窗口最值。暴力 max/min 会超时,于是就要用单调队列维护窗口内部的有效候选。
一看到动态最值、Top K、多路归并,就想优先队列
堆的关键词不是"排序",而是"动态维护"。候选集一直在变,但每一步只要其中一个最值,这时优先队列就很自然。
栈与队列常见模板
普通栈模板
Python3
点击展开代码
展开代码
普通队列模板
Python3
点击展开代码
展开代码
双端队列模板
Python3
点击展开代码
展开代码
括号匹配模板
Python3
点击展开代码
展开代码
单调栈模板
Python3
点击展开代码
展开代码
单调队列模板
Python3
点击展开代码
展开代码
优先队列模板
Python3
点击展开代码
展开代码
栈与队列问题总结
这一篇内容看起来很多,但其实核心只有几句话:
- 栈解决最近、嵌套、回退
- 队列解决顺序推进、层序扩散、最短步数
- 单调栈解决"第一个更大/更小"和边界
- 单调队列解决滑动窗口最值
- 优先队列解决动态候选集里的最值问题
如果要把整篇再压缩成一句话,那就是:
不要先背题,而是先判断题目需要维护哪一种顺序。当你开始能从"维护顺序"的角度看题时,栈、队列、单调栈、单调队列、优先队列这几个专题就会越来越像一家人,而不是零散的很多模板。
专题阅读
算法
这篇文章属于同一条阅读链。你可以直接在这里切换,不用再回到列表页重新找。
部分信息可能已经过时
留言区
留言
欢迎纠错、补充、交流。昵称和评论内容必填;如果你愿意,也可以留下联系方式,仅站主可见。