这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战
前言
关于 LeetCode 数组类型题目的相关解法,可见LeetCode 数组类型题目做前必看,分类别解法总结了题目,可以用来单项提高。觉得有帮助的话,记得多多点赞关注哦,感谢!
题目描述
给定 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-cn.com/problems/tr…
题解
- 暴力解法. 暴力解法就是在当前位置 i 上, 找出 i 左边的最高点 left, 找出 i 右边的最高点 right, 位置 i 上可存储的水为 min(left, right) - nums[i]. 如下图所示, 在 cur 位置处可存储的水量为 min(2, 3) - 0 = 2, 因此最后结果为找出所有位置上可存储的水的和. 代码见图下方.
每次在左边和右边找最大值的时间复杂度为 O(n), 所以整体算法的时间复杂度为 O(n²), 空间复杂度为 O(n). 当然这种写法不能通过所有算例, 需要减小时间复杂度.
/**
* @param {number[]} height
* @return {number}
*/
var trap = function(height) {
const n = height.length
let ans = 0
for (let i = 0; i < n; ++i) {
l = Math.max(...height.slice(0, i + 1))
r = Math.max(...height.slice(i))
ans += Math.min(l, r) - height[i]
}
return ans
};
- 方法1中, 我们在每个位置上都重新要比较 i 左边的所有值, i 右边的所有值来找出来两边的最大值, 这其中是有重复计算的. 其实, 我们每次可以利用 i - 1 位置左边的最大值来计算 i 位置左边的最大值, 只需要比较 i - 1 左边的最大值和 i 位置的大小即可. 右边同理, 因此可以用两个数组, left 和 right, 分别为从 0 到 n - 1 位置的最大值, 从 n - 1 到 0 位置的最小值. 这也算是一种空间换时间的策略. 具体代码见下方.
时间复杂度为 O(n), 空间复杂度也为 O(n)
/**
* @param {number[]} height
* @return {number}
*/
var trap = function(height) {
const n = height.length
let left = []
let right = []
for (let i = 0; i < n; ++i) {
left[i] = i === 0 ? height[i] : Math.max(left[i-1], height[i])
}
for (let i = n - 1; i >= 0; --i) {
right[i] = i === n - 1 ? height[i] : Math.max(right[i+1], height[i])
}
let ans = 0
for (let i = 0; i < n; ++i) {
ans += Math.min(left[i], right[i]) - height[i]
}
return ans
};
- 双指针法. 根据木桶原理, 装水的多少只取决于短的一方. 我们定义 l, r 两个指针, 和 max_l, max_r 两个变量, 初始化为两端的值. 当 max_l < max_r 时, 我们就可以计算当前 l 可以存储的水量为 max_l - height[l], 并移动 l 向右, 更新 max_l = max(max_l, height[i]), 反之计算 max_r. 其实, 本质来讲本算法是算法2的优化版本, 我们在从两边到中间逼近的过程中来计算 i 左边和右边的最大值, 而算法2是事先计算好两边的最大值. 具体代码见下方.
时间复杂度为 O(n), 空间复杂度为 O(1)
/**
* @param {number[]} height
* @return {number}
*/
var trap = function(height) {
const n = height.length
let l = 0
let r = n - 1
max_l = height[l]
max_r = height[r]
let ans = 0
while (l < r) {
if (max_l < max_r) {
ans += max_l - height[l]
++l
max_l = Math.max(max_l, height[l])
} else {
ans += max_r - height[r]
--r
max_r = Math.max(max_r, height[r])
}
}
return ans
};
- 单调栈法. 我们可以构造一个单调递减的栈, 依次将 height 里元素的下标 push 进栈, 当 height[i] 的值比栈顶对应的高度大时, 说明已经可以构造出一个容器来盛水了, 只需要弹出栈顶元素, 弹出后栈顶元素和当前的 i 位置可以构造出一个容器, 计算出容器的宽度和高度, 就可以得知盛水的多少. 具体见如下代码. 时间复杂度 O(n), 空间复杂度 O(n)
/**
* @param {number[]} height
* @return {number}
*/
var trap = function(height) {
const n = height.length
let stack = []
let ans = 0
for (let i = 0; i < n; ++i) {
while (stack.length > 0 && height[i] > height[stack[stack.length - 1]]) {
const cur = stack.pop()
if (stack.length === 0) break
const l = stack[stack.length - 1]
const w = i - l - 1
const h = Math.min(height[l], height[i]) - height[cur]
ans += h * w
}
stack.push(i)
}
return ans
};