引入
本题是这样的:
42.接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
题目给出的图片一眼就能了然题目要问的是什么。
很明显,某一列能装多少水,取决于该列左侧和右侧的高度的最小值。
最无脑的做法就是,对每一列求其左右的最大值,也就是用额外的两个O(N)大小的数组空间来存储每一列的最大值和最小值。
该方法时间复杂度也是O(N),不过是需要2-3次遍历数组(求最大值一次,最小值一次,然后求盛水高度一次,后面两次可以合并)。
不过,我希望能够一次遍历完成计算,并且不需要使用额外的数组空间,一开始我是这样写的:
class Solution {
public int trap(int[] height) {
int output=0;
int left=height[0];
for(int i=1;i<height.length;i++){
if(height[i]<left) output+=(left-height[i]);
else{
left=height[i];
}
}
return output;
}
}
这样写,值考虑左侧的值,并没有考虑右侧值是否比左侧值更小,当然是错的。不过提供了一种思路,是否能够用相似的方法一次遍历并且不实用额外的数组空间呢?
双指针法
由之前的解法,我们看到,只考虑了一侧的值(左侧),而忽略了另一侧的值,这样是不可取的。因为水位高度取决于最低的那块板子。
所以,我们想到,是否能用双指针,分别来指向左右两侧,然后比较左侧目前的max_height大还是右侧目前的max_height大。其具体步骤是这样的:
- left=0, right=length-1;//两个指针从左右分别开始。
- left_max_height=height[left],right_max_height=height[right];记录下左右两边的目前指针指向的最高的高度。
- 当left<right的时候,判断左边的最大高度大还是右边的最大高度大。如果是右边的最大高度更大,那么左边无论如何都会装上水,因为它是矮的那一块。
- 不妨假设是左边的最大高度小一点,判断目前的height和left_max_height的关系。如果目前的高度比left_max_height高度更高,再次进入步骤3。否则计算盛水量:left_max_height-height。指针left++,重复步骤4直到不满足left<right。
{
int left = 0, right = height.length - 1;
int ans = 0;
int left_max_height = 0, right_max_height = 0;
while (left < right) {
if (height[left] < height[right]) {
if(height[left] >= left_max_height) {
left_max_height = height[left];
}else{
ans += (left_max_height - height[left]);
}
++left;
}
else {
if(height[right] >= right_max){
right_max_height = height[right];
}else{
ans += (right_max_height - height[right]);
}
--right;
}
}
return ans;
}