1 算法简介
给定 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
算法简介来自力扣(LeetCode)42
2 思路
- 思考一个位置接的雨水多少与哪些因素有关?如何计算?
- 如何计算相关因素?
这里暂用下标 i
表示具体的位置,用 num[i]
表示位置 i
接的雨水多少。
对于问题 1:
根据图片示意可知,num[i]
大小由它左边的最高值(left_max
)与右边的最高值(right_max
)中的较小值决定(与桶的容量由最短板决定类似)。
因此 num[i] = Math.min(left_max, right_max) - height[i]
(备注:height[i]
是位置 i
本身的高度,需要减去)。
对于问题 2:
在计算 left_max
与 right_max
时,不同的方法方式略有不同,直接想到的就是以 i 为界逐个比较找到最大值,具体做法将在下面的代码部分详细阐述。
3 解法
这里使用 JavaScript
书写相关解法。
3.1 暴力解法
遍历所有接雨水的位置,分别计算每个位置能接多少雨水,再求和返回即可。
var trap = function (height) {
let res = 0, n = height.length;
if (n === 0) return 0;
// 第一个位置和最后一个位置都接不到雨水
// 所以 i 的范围是 (0, n-1)
for (let i = 1; i < n - 1; i++) {
let left_max = 0, right_max = 0;
// 但计算 left_max 和 right_max 时,需要考虑首尾位置的高度
for (let j = i; j >= 0; j--) left_max = Math.max(height[j], left_max)
for (let j = i; j < n; j++) right_max = Math.max(height[j], right_max);
// 将每个位置能接的雨水加在一起
res += Math.min(left_max, right_max) - height[i];
}
return res;
};
时间复杂度:O(N^2)
空间复杂度:O(1)
3.2 优化
暴力解法中,将求 left_max
和 right_max
的计算都写在了循环里,比较耗时,其实可以先分别计算每个位置的 left_max
和 right_max
,再遍历数组求出每个位置接的雨水多少。
var trap = function (height) {
let res = 0, n = height.length;
if (n === 0) return 0;
let left_max = new Array(n);
let right_max = new Array(n);
left_max[0] = height[0];
right_max[n - 1] = height[n - 1];
// 注意这里计算最大值的不同之处在于
// 位置 i 的左边最大值是前一个位置的左边最大值与 height[i] 比较
// 右边最大值同理
for (let i = 1; i < n - 1; i++) left_max[i] = Math.max(left_max[i - 1], height[i]);
for (let i = n - 2; i >= 0; i--) right_max[i] = Math.max(right_max[i + 1], height[i]);
// 每个位置接的雨水求和
for (let i = 1; i < n - 1; i++) res += Math.min(left_max[i], right_max[i]) - height[i];
return res;
};
时间复杂度:O(N),虽然是遍历了三次,但是分开的,不是嵌套的。
空间复杂度:O(N),使用了两个数组。
3.3 双指针
巧妙解法双指针,优化空间复杂度。
var trap = function (height) {
let res = 0, n = height.length;
if (n === 0) return 0;
// 两个指针
let left = 0, right = n - 1;
// 左右最大值
let left_max = height[0];
let right_max = height[n - 1];
while (left <= right) {
left_max = Math.max(left_max, height[left]);
right_max = Math.max(right_max, height[right]);
if (left_max < right_max) {
res += left_max - height[left];
left++;
} else {
res += right_max - height[right];
right--;
}
}
return res;
};
时间复杂度:O(N)
空间复杂度:O(1)
4 总结
这道题比较经典,直播中字节面试官说是字节面试中出现频率很高的题,同时双指针是比较经典的解法,后面有空再总结同系列的题,谢谢大家。
第一次写技术类文章,如有不当之处,欢迎指出哟(^U^)ノ~YO
本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情