携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第33天,点击查看活动详情
题目描述
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 1:
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
示例 2:
输入: height = [4,2,0,3,2,5]
输出: 9
提示:
n == height.length1 <= n <= 2 * 1040 <= height[i] <= 105
解题思路
老观众都熟悉,这道题有 手摸手提桶跑路——LeetCode11. 盛最多水的容器 内味儿了。
实际上还是 短板理论 的支撑。不过这道题不同的是“板”变成了“柱子”,也就是变粗了,这意味着柱子的宽度不能忽略不计了。
我们仔细看看示例一的图。height[0] 和 height[1] 之间是没有水的,最后一根柱子后面也是没有水的。这意味着当柱子的数量为 n 时,对于任意一根柱子 height[i] 来说,只有左右两边都有柱子的情况下,height[i] 这根柱子才 有可能 可以接到雨水,即 height[0, i) 和 height(i,n-1] 两个区间中,各至少有一根柱子的长度大于 0。
仔细观察,有的柱子 height[i] 即使左右两边有高度大于 0 的其他柱子,好像也接不到雨水嗷。
嗷,具体情况具体分析是吧,行。
其实我们可以发现,在 height[i] 左右两侧都有比 height[i] 还 高 的柱子时,就一定能接到雨水了。
那么我遍历 height 数组,对于每个 height[i],我去左边找到比它更高的柱子 height[left],去右边找到比它更高的柱子 height[right],根据短板理论,短的那根柱子决定接水量,所以短板 minHeight = Math.min(height[left], height[right]),那么当前柱子的接水量就是 minHeight - height[i]。最后累加就完事了。
最后,点击执行代码!
答案错了,少了1滴水,为啥呢。
我检查了一下,问题出在这里,我中间高度为 0 的柱子,可接水量为 2,但是实际上代码只给我算成 1,因为找的是 height[i] 左右比它高的柱子,找到就完事了,所以只找到了比较短的那两根柱子。而实际上它真正容量为 Math.min(左右两边高度最高的柱子) 决定的。
这逻辑就没问题了,提交代码,通过,撒花~~
题解
var trap = function(height) {
const lens = height.length;
// 找一个左边右边都比当前高的
let res = 0;
for(let i=0; i<lens; ++i) {
let left = i - 1, right = i + 1, lMax = height[i], rMax = height[i];
while(left >= 0) {
lMax = Math.max(height[left], lMax);
left--;
}
while(right < lens) {
rMax = Math.max(height[right], rMax);
right++;
}
if(lMax != 0 && rMax != 0) {
res += Math.min(lMax, rMax) - height[i];
}
}
return res;
};
优化
老观众都知道了吧,我们是什么?
我们是顶级前端好吧,这个执行用时 1s 不得优化一下吗?
这里耗时的地方应该是对于每个柱子 height[i] 都要找到左右两边最高的柱子。我一下子就想到 最高温度 了,那题找第i天右侧第一高温度是过几天,用的单调栈,那我这题是找两边最高的,是不是也能用单调栈。
于是乎我维护了一份左侧单调栈和右侧单调栈,每次查找左右最高柱子时就只要读取值,提升了不少效率。
题解
/**
* @param {number[]} height
* @return {number}
*/
var trap = function(height) {
const lens = height.length;
const stack = [];
let max = 0;
for(let i=0; i<lens; ++i) {
if(height[i] > max) {
max = height[i];
}
stack.push(max);
}
const stack2 = [];
max = 0;
for(let i=lens-1; i>=0; --i) {
if(height[i] > max) {
max = height[i];
}
stack2.push(max);
}
return height.reduce((f, c, i)=>{
return f += Math.min(stack[i], stack2[stack2.length-i-1]) - c;
}, 0);
};
题外话
其实后面单调栈的优化我原本是用一个单调栈省去左侧遍历的,因为右侧我不太熟练,想着要遍历两次生成两个栈,有点怪怪的。测试发现一个单调栈在第一版的基础上效率快了一倍,从 1000ms 到 500ms,两个单调栈效率从 1000ms 到 70ms,基本上都快翻了一倍。
想起个笑话,老板急着赶需求,让程序加班,程序说要一个月才能完成,老板想着再招个人,15天应该就搞定了吧,按这个理论30个人一天就能搞定了。
这道题让我有了不少收获,学以致用,只有真正理解的东西才是属于自己的。一个单调栈会用,两个就懵逼了,这是很严重的问题,暴露出我的不足之处,日后还是要静下心来,勤能补拙。