跳到正文
CHAOS·IO

动态规划入门

单调栈:从「柱状图中最大的矩形」说起

把 O(n²) 的区间最小值枚举压到 O(n),顺便说清楚单调栈到底在维护什么。

本页目录
  1. 问题
  2. 换一个问法
  3. 单调栈
  4. 复杂度
  5. 什么时候想到它

问题

给定一个柱状图的高度数组 hh,求柱状图中能勾勒出的最大矩形面积。

暴力做法是枚举左右边界 O(n2)O(n^2),再用区间最小值算面积,还是 O(n3)O(n^3)。我们想把复杂度压到 O(n)O(n)。

换一个问法

与其枚举区间,不如换个角度:枚举“谁是最低的那根柱子”。

如果第 ii 根柱子作为矩形里最低的柱子,那么矩形的左右边界分别是:

  • LiL_i:左边第一个严格小于 hih_i 的位置;
  • RiR_i:右边第一个严格小于 hih_i 的位置。

于是以 ii 为最低柱的最大矩形面积就是:

Ai=hi×(Ri−Li−1)A_i = h_i \times (R_i - L_i - 1)

答案取 max⁡Ai\max A_i。现在问题变成:怎么在线性时间内求出每个位置的左右第一个更小值?

单调栈

维护一个栈,栈里存下标,对应的高度单调递增(栈底到栈顶)。从左往右扫描:

单调栈的入栈与出栈

对每个位置 ii:

  1. 当栈非空且 h[栈顶]≥hih[\text{栈顶}] \ge h_i 时,弹出栈顶 jj。此时 ii 就是 jj 的右边界(第一个更小的位置)。
  2. 弹完后若栈还非空,栈顶就是 jj 的左边界;若栈空,说明 jj 左边没有更小的,Lj=−1L_j = -1。
  3. 把 ii 入栈。

每个下标最多入栈一次、出栈一次,所以总复杂度是 O(n)O(n)。

复杂度

  • 时间:O(n)O(n),每个元素进出栈各一次。
  • 空间:O(n)O(n)。

什么时候想到它

经验上,当题目出现下面这些信号时,可以往单调栈上想:

信号 例子
求“每个位置左右第一个更大/更小的元素” 每日温度、柱状图最大矩形
需要 O(n) 求出所有区间的最优值 接雨水、跳跃游戏 II
括号匹配、滑动窗口最值 表达式求值

关键词是 “第一个” 和 “连续区间”。如果题目问的是“任意区间”,那多半该往线段树或 ST 表上想。