第3章|栈与队列
Unit 13 · 综合加练:单调栈识别凹槽边界
水不是在“低柱子”上产生,
而是在左右边界同时出现时被确定。
本单元把单调栈从“寻找下一个更大元素”推进到二维几何:当前更高柱到达时,弹出的索引是凹槽底部;新栈顶与当前索引构成左右边界;每次弹栈计算一层互不重叠的水。
题目要求
给定 n 个非负整数表示宽度均为 1 的柱子高度,计算这些柱子在下雨后能够接住多少单位的水。
输入、输出与目标复杂度
- 输入:非负整数数组
height; - 输出:所有柱子之间可存储的水量总和;
- 本页主方法:单调栈,时间
O(n),空间O(n); - 迁移方法:双指针,时间
O(n),空间O(1)。
示例
输出:6
输出:9
数据约束
n == height.length1 ≤ n ≤ 2 × 10⁴0 ≤ height[i] ≤ 10⁵
被当前更高柱弹出的索引。它是本次新水层的底部高度,而不是左边界。
弹出底部后新的栈顶。若栈已空,说明左侧没有边界,本次不能形成封闭凹槽。
min(left, right) - bottom。较矮边界决定水面高度。
每次弹栈计算一层水:width × bounded_height,不同弹栈层互不重复。
Unit 12 → Unit 13|从“关闭括号恢复父上下文”到“右边界到达结算历史凹槽”
左括号到达时保存尚未完成的父层状态;右括号是关闭事件,弹出最近父层并完成合并。
下降柱高到来时保存尚未封闭的边界;更高右柱是关闭事件,弹出凹槽底部并完成水层结算。
未解决边界索引栈(栈底 → 栈顶)
本次弹栈计算
弹出的柱子是凹槽底部;弹出之后暴露出的新栈顶才位于底部左侧。
水会从较矮一侧溢出,因此水面不可能超过 min(left, right)。
位置 2 上方的最终水深是多少?左侧最高 4,右侧最高 5,当前柱高 0。
min(4,5),再减去柱高 0。提示 1|容器保存什么? stack 保存索引,因为需要同时访问柱高并计算左右宽度。 提示 2|什么时候弹栈? 只有 current_height 严格高于栈顶柱高,当前柱才可能成为新的右边界。 提示 3|三种角色不能混淆 bottom = stack.pop() left = 弹栈后的 stack[-1] right = 当前扫描索引 提示 4|面积公式 width = right - left - 1 bounded_height = min(height[left], current_height) - height[bottom] water += width * bounded_height 请回到填空区独立完成八个表达式。
迁移题:LeetCode #11《盛最多水的容器》
先用 #42 的双指针异解理解“较矮一侧为什么可以被安全处理”,再迁移到一个新的官方问题:不再累加每个位置的雨水,而是在所有左右边界对中选择容量最大的一个容器。
从横向水层改为纵向逐点结算
#42 的单调栈保存未封闭凹槽;双指针只保存 left/right/left_max/right_max。比较两端后,较矮一侧的外部边界已经足够,当前位置可以立即结算。这个原则将成为 #11 贪心移动的认知起点。
LeetCode #11|盛最多水的容器 · Medium
数组 · 双指针 · 贪心
官方题意
给定长度为 n 的数组 height。第 i 条垂线从 (i,0) 延伸到 (i,height[i])。选择两条垂线,与 x 轴构成不能倾斜的容器,返回它能够容纳的最大水量。
官方样例与约束
n == height.length2 ≤ n ≤ 10⁵0 ≤ height[i] ≤ 10⁴
读题检查 1|题目最终返回什么?
#42 → #11|相同的“较矮边界”,不同的目标函数
min(hL,hR) × (R-L)四个语义槽位|把贪心证明编译成代码
官方样例轨迹|height = [1,8,6,2,5,4,8,3,7]
可选挑战|脱离槽位独立重写 #11
检查器只验证双指针、面积公式、移动较矮边界和返回最大值,不会自动填入完整答案。
Unit 13 Skill 卡
看到“两个边界共同决定容量”时,先判断目标是累计所有局部容量,还是从所有边界对中选择一个全局最优。#42 需要计算所有凹槽水量;#11 只保留当前最大容器,并通过移动较矮边界排除不可能更优的候选。