题目:
Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining.
For example,
Given [0,1,0,2,1,0,1,3,2,1,2,1], return 6.
The above elevation map is represented by array [0,1,0,2,1,0,1,3,2,1,2,1]. In this case, 6 units of rain water (blue section) are being trapped. Thanks Marcos for contributing this image!
刚开始看到这个题目思绪有一些混乱,不知道从那个点开始入手。其实Hard级别的题目总会让人有这种错觉,可能一开始映入脑海了一个比较直接的思路,但是一想,哎呦不行,好多情况没考虑到。然后就开始各种想这想那,无从下手==
我在纠结了一段时间之后,决定开始使用最原始的暴力解法,对每个元素,搜寻其存水量,然后遍历数组求和即可。这样我们需要写一个函数来实现针对每一个元素找到其左右两边最大的元素,也就是其存水量。代码入下:
//超时
public int trap(int[] height) {
int res = 0;
//遍历所有元素,对每个元素计算其存水量,并求和
for(int i=1; i<height.length-1; i++){
res += find(height, i);
}
return res;
}
public int find(int[] height, int i){
int left=0, right=0;
//找到该元素左边最大的数
for(int j=0; j<i; j++)
left = Math.max(left, height[j]);
//找到该元素右边最大的数
for(int j=i+1; j<height.length; j++)
right = Math.max(right, height[j]);
//如果元素本身大于两边的数字,则无法存水,否则可以存放左右两边较小的那个数减去钙元素值大小。
if(height[i] < left && height[i] < right)
return Math.min(left, right) - height[i];
else
return 0;
}
很明显,上面的解法存在过多的重复操作,程序运行超时在意料之内。接下来我们在思考如何改进。其实我们明白本题的重点就在于如何高效的找到一个元素的存水量。一个比较容易想到的思路应该是分别从两边开始遍历,用两个变量记录两侧的最大数,可是这样有一个缺点是我们没有办法确定每个元素的存水量,后来自己对着上面的图片看了半天,发现我们并没有必要分别从两侧同时进行,现在只考虑单向,从左往右,我们首先记录一个最大值,然后向后遍历,当遇到比该最大值更大的元素时,将这两个元素中间的元素进行计算存水量,然后一直向后遍历,当遇到整个数组的最大值是时,后面的元素就反向操作即可。那么我们首先来简化一下思路:
- 找到数组最大值
- 左边的按照上述操作进行遍历和计算
- 右边的从后向前计算即可
代码入下所示:
//60%
public int trap2(int[] height){
int res=0, max=0, left=0, right=height.length-1;
//找到数组最大值索引
for(int i=1; i<height.length; i++)
if(height[i] > height[max])
max = i;
//最大值左边的,每个元素的需水量=左边的最大值-该元素值。实时更新左边的最大值即可
for(int i=1; i<max; i++){
if(height[i] < height[left])
res += height[left] - height[i];
else
left = i;
}
//最大值右边的,原理同上,反向操作即可
for(int i=height.length-2; i>max; i--){
if(height[i] < height[right])
res += height[right]-height[i];
else
right = i;
}
return res;
}
其实对于上面的代码,我一开始是写成下面这样,没有考虑最大值右边,然后提交之后根据错误答案慢慢推敲出来的==
public int trap1(int[] height) {
int res = 0, left=0;
for(int i=1; i<height.length; i++) {
if (height[i] >= height[left]) {
int tmp = height[left];
while (++left < i) {
res += tmp - height[left];
}
}
}
while(++left<height.length-1)
res += Math.max(0, height[height.length-1] - height[left]);
return res;
}
接下来,我又去discuss里面看了一下别人的思路,找到了一个可以击败88%用户的方法。代码入下:
//88%
public int trap3(int[] A) {
int i = 0, j = A.length - 1, result = 0, plank = 0;
while(i <= j){
//记录最小值
plank = plank < Math.min(A[i], A[j]) ? Math.min(A[i], A[j]) : plank;
//如果A[i]比较小,则将i的存水量加起来并i++,否则计算j的存水量并j++
result = A[i] >= A[j] ? result + (plank - A[j--]) : result + (plank - A[i++]);
}
return result;
}