06 · 栈与堆

来源: 代码随想录栈与队列专题 + 单调栈模板 + labuladong 堆的讲解
核心价值: 栈解决”后进先出”场景,堆解决”动态最值”场景


一、本质理解

  • 栈(Stack): 受限的线性表,LIFO(后进先出),底层是数组或链表
  • 堆(Heap / 优先队列): 用数组实现的完全二叉树,自动维护最值
  • 单调栈(Monotonic Stack): 栈中元素保持单调递增或递减,用于找”下一个更大/更小元素”

二、栈

核心应用

场景说明典型题目
括号匹配左括号入栈,右括号匹配栈顶20. 有效的括号
最小值追踪辅助栈存当前最小值155. 最小栈
表达式求值数字栈 + 操作符栈394. 字符串解码

栈的通用模板

stack = []
for item in items:
    if "需要入栈":
        stack.append(item)
    else:
        # 出栈处理
        val = stack.pop()

单调栈模板(找下一个更大元素)

def next_greater_element(nums):
    n = len(nums)
    res = [-1] * n
    stack = []  # 存索引
    
    for i in range(n):
        # 当前元素比栈顶大:找到"下一个更大元素"
        while stack and nums[stack[-1]] < nums[i]:
            idx = stack.pop()
            res[idx] = nums[i]
        stack.append(i)
    
    return res

单调栈变体:

  • 找下一个更大元素 → 单调递减栈(栈底到栈顶递减)
  • 找下一个更小元素 → 单调递增栈(栈底到栈顶递增)
  • 找上一个更大元素 → 反向遍历

柱状图中最大矩形(单调栈经典应用)

def largest_rectangle_area(heights):
    stack = []  # 存索引,维护递增栈
    heights = [0] + heights + [0]  # 加哨兵,避免空栈判断
    max_area = 0
    
    for i in range(len(heights)):
        while stack and heights[stack[-1]] > heights[i]:
            h = heights[stack.pop()]
            w = i - stack[-1] - 1
            max_area = max(max_area, h * w)
        stack.append(i)
    
    return max_area

三、堆(优先队列)

Python 堆操作(heapq 模块)

import heapq
 
# 最小堆
heap = []
heapq.heappush(heap, val)  # 入堆
min_val = heapq.heappop(heap)  # 弹出最小值
min_val = heap[0]  # 查看最小值(不弹出)
 
# 大根堆:存负值
heapq.heappush(heap, -val)
max_val = -heapq.heappop(heap)
 
# 将数组堆化 O(n)
heapq.heapify(arr)
 
# Top K 最大:用最小堆维护
def top_k_largest(nums, k):
    heap = []
    for num in nums:
        heapq.heappush(heap, num)
        if len(heap) > k:
            heapq.heappop(heap)
    return heap[0]  # 第 k 大

经典应用:数据流的中位数(双堆)

from heapq import heappush, heappop
 
class MedianFinder:
    def __init__(self):
        # 大根堆存较小一半(Python 无大根堆,存负值)
        self.small = []  # max-heap
        # 小根堆存较大一半
        self.large = []  # min-heap
    
    def add_num(self, num):
        heappush(self.small, -num)
        # 确保 small 中所有元素 <= large 中所有元素
        if self.small and self.large and (-self.small[0]) > self.large[0]:
            val = -heappop(self.small)
            heappush(self.large, val)
        # 平衡两堆大小
        if len(self.small) > len(self.large) + 1:
            val = -heappop(self.small)
            heappush(self.large, val)
        if len(self.large) > len(self.small):
            val = heappop(self.large)
            heappush(self.small, -val)
    
    def find_median(self):
        if len(self.small) > len(self.large):
            return -self.small[0]
        return (-self.small[0] + self.large[0]) / 2.0

四、Hot 100 栈与堆题目清单

题号题目难度核心技巧建议用时
20有效的括号Easy栈匹配20 min
155最小栈Medium辅助栈25 min
394字符串解码Medium双栈/递归30 min
739每日温度Medium单调栈30 min
84柱状图最大矩形Hard单调栈+哨兵40 min

题号题目难度核心技巧建议用时
215第 K 个最大元素Medium快速选择/堆35 min
347前 K 个高频元素Medium哈希+最小堆30 min
295数据流中位数Hard双堆40 min

五、易错点与技巧

  1. Python 没有大根堆:想要大根堆就存负值,取的时候再转回来
  2. 单调栈存索引而非值:因为很多时候需要索引来计算距离或宽度
  3. 柱状图加哨兵:在 heights 两端加 0,避免栈空判断和最后清空栈
  4. Top K 技巧:求第 K 大用最小堆(堆大小 = K),求前 K 大也一样
  5. 堆的时间复杂度:push 和 pop 都是 O(log k),建堆 O(n)

六、复杂度总结

结构操作时间复杂度空间复杂度
入栈/出栈O(1)O(n)
单调栈一次遍历O(n)O(n)
push/popO(log n)O(n)
建堆O(n)O(n)

七、参考与延伸