一、题目
给定一个直方图(也称柱状图),假设有人从上面源源不断地倒水,最后直方图能存多少水量?直方图的宽度为 1。
上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的直方图,在这种情况下,可以接 6 个单位的水(蓝色部分表示水)。 感谢 Marcos 贡献此图。
示例:
输入: [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/volume-of-histogram-lcci
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
二、解题方法
这道题其实给我的启示挺多,最大的启发还是
不要一条路走到黑
,开始的时候思路其实是去寻找波谷,但是是实在困难,逻辑会非常复杂,看下图示例:
如果要去寻找波谷做这道题,一个小波谷外面可能有一个更大的波谷,更大的波谷外可能有个更大的波谷,显然,逻辑会非常复杂。那么,如何简化呢?当我们发现找一个集体的总存储量会比较难的时候,我们其实是可以去思考是否可以把每一个位置的存储量单独求出来呢?如果可以实现的话,只需要遍历一次数组就可以找到总存储量了。
注:很明显,数组左右的边界位置是无法存储水的,中间每一个位置的存储量其实是等于
Math.max(Math.min(maxLeft, maxRight)-height[i], 0)
,其中,height[i]为当前位置的高度,maxLeft和maxRight分别为当前位置左右两边的最高高度,如果不理解这个代码可以看一下下面两张图
【解释】如下图,当当前位置比左右最大值都小时,能存储的最大水量为箭头的高度。
当前位置如果是最高的,必然不能存储任何水量
1)暴力法
class Solution {
public int trap(int[] height) {
/*
* 1、凡是对象都要进行null值判断,否则就可能出现空指针异常
*/
if(height==null||height.length<3)return 0;
int waterAmount=0;
int n=height.length;
for(int i=1;i<n-1;i++) {
int maxLeft=Integer.MIN_VALUE;
int maxRight=Integer.MIN_VALUE;
for(int left=0;left<i;left++)
maxLeft=maxLeft>=height[left]?maxLeft:height[left];
for(int right=n-1;i<right;right--) {
maxRight=maxRight>=height[right]?maxRight:height[right];
}
waterAmount+=Math.max(Math.min(maxLeft, maxRight)-height[i], 0);
}
return waterAmount;
}
}
时间复杂度O(n^2),空间复杂度O(1)
2)预处理数组优化
我们发现,在暴力解法中,每次遍历都要用O(n)去寻找左边和右边的最大值,这样做显然是不好的。这时,我们可以采用预处理数组的方法,仅通过两次遍历就把所有位置当前位置的最大值寻找到即可。即leftMax[i]代表数组中从0到i的最大值,rightMax[i]代表数组中从i到n-1的最大值。
class Solution {
public int trap(int[] height) {
if(height==null||height.length<3)return 0;
int waterAmount=0;
int n=height.length;
int[] leftMax=new int[n];
leftMax[0]=height[0];
//从左边遍历
for(int i=1;i<n;i++) {
leftMax[i]=height[i]>=leftMax[i-1]?height[i]:leftMax[i-1];
}
int[] rightMax=new int[n];
rightMax[n-1]=height[n-1];
//从右边遍历
for(int i=n-2;i>=0;i--) {
rightMax[i]=height[i]>=rightMax[i+1]?height[i]:rightMax[i+1];
}
for(int i=1;i<n-1;i++) {
waterAmount+=Math.max(Math.min(leftMax[i-1],rightMax[i+1])-height[i], 0);
}
return waterAmount;
}
}
时间复杂度O(n),空间复杂度O(n),这是一个很典型的空间换时间的例子
3)双指针进行优化
预处理数组方法在时间复杂度优化已经做的非常好了,但是会牺牲空间代价。我们知道,双指针不仅可以优化时间复杂度,而且不需要额外的空间代价。
class Solution {
public int trap(int[] height) {
if(height==null||height.length<3)return 0;
int waterAmount=0;
int n=height.length;
int left=1;
int right=n-2;
int leftMax=height[0];
int rightMax=height[n-1];
while(left<=right) {
if(leftMax<=rightMax) {
waterAmount+=Math.max(leftMax-height[left], 0);
/*如何简化下面代码?用Math.Max
* leftMax=leftMax>=height[left]?leftMax:height[left]; left++;
*/
leftMax=Math.max(height[left++], leftMax);
}
else {
waterAmount+=Math.max(rightMax-height[right], 0);
rightMax=Math.max(height[right--], rightMax);
}
}
return waterAmount;
}
}
时间复杂度O(n),空间复杂度O(1)