本文正在参加「Java主题月 - Java 刷题打卡」,详情查看活动链接
接雨水 给定 n 个非负整数表示每个宽度为 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 个单位的雨水(蓝色部分表示雨水)。
输入:height = [4,2,0,3,2,5] 输出:9
来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/tr… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思考
思考每个位置什么时候才会有蓄水?蓄水量跟什么有关?
题目分析
首先,我们思考每一列水的高度,即每列有多少水?
显然,这取决于左侧最高的柱子与右侧最高的柱子的最小值。
因此,我们可以得出:
对于长度为N的数组height,height[i]所能存储的水取决于i左侧最大值leftMax与右侧最大值rightMax中的小的那一个,其高度减去height[i],若为正,则表示i处会有蓄水。
i左侧的最高柱子表示为:(带上i并不影响最终运算。)
leftMax_i = Max(height[0,...,i])
i右侧最高的柱子表示为:
rightMax_i = Max(height[i,...,N-1])
i处的蓄水量water_i伪代码可表示为:
water_i = Max(0, Min(leftMax_i, rightMax_i)-height[i])
因此,若从0~N-1依次遍历,我们很容易得出每个位置上的蓄水量,进而解决了此问题。
优化1:每一步的计算都有用
思考这样一个问题,如果我们依照上述方法,从0~N,即从左向右依次求解各个位置的蓄水量,在计算i位置的leftMax_i时,是否每次都需要从位置0依次比较呢?
显然是不需要的,我们只需要比较i-1的左侧最高柱子与i-1柱子的高度便可得出i位置左侧的最高柱子,即:
leftMax_i = Max(leftMax_(i-1), height[i-1])
同理,若我们从N~0,即从右向左依次求解,对于位置i,右侧最高柱子为:
rightMax_i = Max(rightMax_(i+1), height[i+1])
优化2:双指针,总是先计算小的那一个
我们假设两个指针left与right在数组左右两端,分别向中间移动,并分别计算获取每个指针指向柱子的蓄水量。移动时遵循:若height[left]<height[right]则向右移动left指针,反之这种向左移动right指针。
按照这种方式,我们考虑这样一种中间状态:
假设0<=left<right<=N-1, 同时,我们知道left的最大左柱高度leftMax_left,同时知道right的最大右柱高度rightMax_right,如何做下一步的运算?
思考ing...
假如,height[left] < height[right],则left最大右柱高度一定大于等于height[right],即三者的大小关系为:
rightMax_left >= height[right] > height[left]
且既然按照每次移动小的,两个指针能移动到此处,且height[left]<height[right],则潜在的有(不然,就卡在最大柱子过不来了):left的左最大柱高度小于等于right的右最大柱高度,即
leftMax_left <= rightMax_left
因此,left处蓄水为:leftMax_left - height[left],
同理,当height[right]<height[left]时,right处蓄水为:rightMax_right - height[right]。
实现代码
/**
* 双指针实现代码参考:https://leetcode-cn.com/problems/trapping-rain-water/solution/javaliang-chong-jie-fa-dpshuang-zhi-zhen-2679/
**/
class Solution {
public int trap(int[] height) {
int ans = 0;
int l = 0, r = height.length - 1;
int lMax = 0, rMax = 0; //分别记录左右最大值
while (l < r) {
lMax = Math.max(lMax, height[l]); //更新左边最大值
rMax = Math.max(rMax, height[r]); //更新右边最大值
if (height[l] < height[r]) { //左边高度比右边小
ans += lMax - height[l]; //计算左边最大高度与当前高度差
++l;
} else {
ans += rMax - height[r]; //计算右边最大高度与当前高度差
--r;
}
}
return ans;
}
}
总结
- 首先确定如何获取每一列上的蓄水量如何计算,再考虑后续优化的问题。
- 理解为什么当
height[left]<height[right]时,潜在的leftMax(left)<=rightMax(right)是难点,这个潜在原因是因为这种移动策略总是移动小的,因此小的数字的那一侧的最大值也总是小的,若不然,就会卡在最大的那个那根柱子上移动不过来。