题目描述
题目链接: . - 力扣(LeetCode)
给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
思考过程
1. 能接雨水的量,取决于左右柱子中较小的那个(短板效应,长的接不住)
2. 取出较小柱子的值,还需要减去柱子的高度(也就是对应的height)
3. 将每个可以容纳雨水的格子累加起来,就是题解
解法
有了思路,我们就开始尝试按照思路写解法,最容易想到的肯定就是暴力循环,两边分别找到最大值,然后把得到的结果叠加,咱们先试试
/**
* @param {*} height
* @return {*}
*/
function tarp (height) {
// 初始化最大能接雨水
let result=0;
// 遍历数组元素
for(let i=0;i<height.length;i++)
{
// 初始化左边与右边最大元素为当前元素
let leftMax=height[i],rightMax=height[i];
// 遍历当前元素左边所有元素,找到最大值
for(let left=i-1;left>=0;left--)
{
if(height[left]>leftMax){
leftMax=height[left];
}
}
// 遍历当前元素右边所有元素,找到最大值
for(let right=i+1;right<height.length;right++)
{
if(height[right]>rightMax)rightMax=height[right];
}
// 计算其位置能接的雨水量,并将其相加
result+=Math.min(leftMax,rightMax)-height[i];
}
// 返回结果
return result;
}
仔细观察代码,咱们可以发现暴力算法,对于每个元素我们都要镜像两次遍历(左,右分别寻找),复杂度无疑来到了O(n^2),那有没有办法可以直接知道两边的最大值,然后通过O(n)就能计算出总量
我们是否可以用一个元素记录最大值max,然后分别用左右两个数组记录左右两个柱子的最大值,这样我们就能在O(n)的时间得到每个位置两侧的最大高度
想到了咱们就试试 !
const collectRainWater = (height) => {
// 定义最大值
let max = 0
// 存储结果
let result = 0
// 左侧数组
const leftMax = []
// 右侧数组
const rightMax = []
for (let i = 0; i < height.length; i++) {
leftMax[i] = max = Math.max(height[i],max)
}
// 重置最大值
max = 0
for (let j = height.length-1; j >= 0; j--) {
rightMax[j] = max = Math.max(height[j],max)
}
for (let k = 0; k < height.length; k++) {
result = result + Math.min(leftMax[k], rightMax[k]) - height[k]
}
return result
}
可以明显看到,比上述暴力算法代码看起来更为直观,同时也降低了复杂度.
不过还是可以发现,咱们的循环次数有点多,是否可以再次基础上继续优化呢?
ps: 笔者也是只想到了上述解法,观看官方题解和评论区大佬的留言,发现了更为简单的解法
即使用双指针算法,记录左右最大元素,并且使用左右指针计算当前元素左右的最大元素
是不是听起来有点绕, 简单来说(说人话)
左右指针分别移动时候和给定数组height的源于比较,取出较大的存到leftMax和rightMax中
遍历结束条件是左右指针相遇
其中会有两种情况,
当左侧最大值小于右侧最大值时候,此时能接到的水量为leftMax-height[left]
当右侧最大值小于左侧最大值时候,此时能接到的水量为rightMax-height[right]
同时需要移动左右指针, 左指针右移,右指针左移
下面附上代码
const trap = height => {
let result= 0;
let left = 0;
let right = height.length -1;
let leftMax = 0;
let rightMax = 0;
while (left < right) {
// 左侧最大值
leftMax = Math.max(leftMax, height[left]);
// 取右侧的最大值
rightMax = Math.max(rightMax, height[right]);
if (leftMax < rightMax) {
// left++左指针右移
count += leftMax - height[left++];
} else {
// right--右指针左移
count += rightMax - height[right--];
}
}
return result;
}
完美解决问题,只需要遍历一次数组,空间复杂度是O(1),时间复杂度也只需要遍历一侧height,是O(n)