接雨水
接雨水经典题:可用双指针或单调栈;单调栈做法把弹出的柱子视为凹槽底部。
#resource / algorithm
#type / snippet
#status / growing
#source / leetcode
#algo / two-pointers
#algo / monotonic-stack
#ds / stack
[!info] related notes 算法面试题型 MOC 栈 单调栈
接雨水
题目链接:https://leetcode.cn/problems/trapping-rain-water/
题型判断
这题有两种主流做法:
- 双指针
- 单调栈
如果从栈角度理解,它属于“单调栈 + 凹槽结算”题。
单调栈思路
维护一个按高度单调递减的栈,栈里放下标。
当当前柱子 height[i] 比栈顶更高时,说明出现了右边界,可以尝试结算一段雨水。
弹出中间低柱 mid 后:
- 新栈顶
left是左边界 - 当前
i是右边界 mid是凹槽底部
可接水高度:
min(height[left], height[i]) - height[mid]
宽度:
i - left - 1
双指针思路
双指针做法的核心是:
- 哪一侧更低,就移动哪一侧
- 移动时用该侧历史最高值结算当前位置能接的水
这是这题最常用的面试写法。
分析
同样是哪边的柱子高度小就移动哪边。 移动的时候计算新位置这一列能接多少雨水。移动的这一侧需要维护一个历史最大高度,再用这个高度减去当前位置高度。
function trap(height: number[]): number {
const n = height.length;
let left = 0, right = n - 1;
let leftMax = height[left], rightMax = height[right];
let ans = 0;
while (left < right) {
if (height[left] <= height[right]) {
left++;
leftMax = Math.max(leftMax, height[left]);
const count = leftMax - height[left];
if (count > 0) ans += count;
} else {
right--;
rightMax = Math.max(rightMax, height[right]);
const count = rightMax - height[right];
if (count > 0) ans += count;
}
}
return ans;
}
单调栈 JavaScript
var trap = function(height) {
const stack = [];
let ans = 0;
for (let i = 0; i < height.length; i++) {
while (stack.length && height[i] > height[stack[stack.length - 1]]) {
const mid = stack.pop();
if (!stack.length) break;
const left = stack[stack.length - 1];
const h = Math.min(height[left], height[i]) - height[mid];
const w = i - left - 1;
ans += h * w;
}
stack.push(i);
}
return ans;
};
一句话总结
单调栈做法里,被弹出的柱子不是边界,而是凹槽底部;左右边界由弹栈后的栈顶和当前柱子共同决定。