题目描述
给你一个数组,数组中的每个数代表一堵墙的高度,两堵墙中的凹地区会存有水,请你求出一共有多少格水?
例如,数组arr=3,2,4,1,5
则图中红色部分共装了1+3共4格水
思路分析
此题,最先容易想到的思路就是,寻找凹处,然后把凹处挨个计算,但是,这个思路有个问题,那就是,如果你已经挨个统计了很多凹处的水,此时,突然发现,左右各出现一个特别高的墙,相当于中间部分全是凹处,此时,前面凹处的计算就不对了。
因此,此题在思路上,确实有一定难度,因此,类似于洗衣机问题,如果一个题目的思路无从下手,可以考虑站在某一个个体的视角考虑问题。
就像此题,我们可以统计出每一个墙上面的水有多少格,然后最终汇总,就是整体有多少格水,而每一个具体的墙上面的水,可以通过其左右区间内的最大值来寻找,左右区间的最大值中较小的那个,就是木桶原理中的门槛,超过部分会流走。因此,这个位置上面的水的多少就是左右区间的最大值中较小值,减去当前的高度。
如何代码实现统计左右区间的最大值呢?
最笨方法,原地左右遍历,时间复杂度O(n^2)。
优化方法,申请两个辅助数组,一个数组保存当前位置左边最大值,一个数组保存当前位置右边最大值,先遍历两边,更新左右两边最大值的辅助数组。然后,再遍历原数组,每遍历到一个位置,就去这两个辅助数组中查当前位置左右的最大值,总共遍历三遍,时间复杂度O(n),空间复杂度O(n)
最优解法,设定两个指针,一个指向最左边,一个指向最右边,设定两个变量,分别记录左右的最大值。然后,左右第一个数,分别赋值给左右的最大值变量,因为边界上的值不可能产生水,此时,左右最大值的区间其实还都只有边界这一个数,对吧?然后,找出左右最大值中较小的那个,指向他的指针向中间移动,来到下一个位置,然后,对比这个位置是否能产生水,如果这个数大于临近的那个最大值,则不产生水,同时,最大值更新为他,如果小于,则产生水,最大值不更新。当左右指针碰面时结束。
为什么这样操作?
首先,为什么要找左右区间最大值中较小的那个?因为,他就是临近位置的左右最大值中较小的。因为指针是从两端开始的,此时,找到较小的最大值临近的位置,这个位置上,另一边区间的最大值,肯定大于临近的最大值。因为无论另一边最大值往后遍历的时候如何更替,反正只可能变大,不可能变小,但是,此时就已经比他临近的这边的最大值还大了,因此,这个临近的最大值,就是这个位置两边区间内较小的最大值。
代码
public static int f(int[] arr){
int leftMax=arr[0];
int rightMax=arr[arr.length-1];
int left=0;
int right=arr.length-1;
int sum=0;
while (left<right){
if(leftMax<rightMax){
left++;
if(leftMax>arr[left]){
sum+=leftMax-arr[left];
}else {
leftMax=arr[left];
}
}else {
right--;
if(rightMax>arr[right]){
sum+=rightMax-arr[right];
}else {
rightMax=arr[right];
}
}
}
return sum;
}