直方图的水量
给定一个直方图(也称柱状图),假设有人从上面源源不断地倒水,最后直方图能存多少水量?直方图的宽度为 1。
上面是由数组 [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
方法一:按行求解
最好理解的方法
思路:
可以看做是总体积减去物品体积
left,right,找层高high的边界,然后每一层的边界left-right+1就是每一层的体积
,但是如果柱子很高的话,时间复杂度就会很高,可优化!
public static int trap(int[] height) {
//统计体积
int V=0;
int high=1;
int left=0,right=height.length-1;
while(left <= right) {
while(left <=right && height[left] < high) {
left++;
}
while(left <=right && height[right] < high) {
right--;
}
V+= (right-left+1);
high++;
}
//物品体积
int sum=0;
for (int i = 0; i < height.length; i++) {
sum+=height[i];
}
return V-sum;
}
方法二:动态规划
对于下标 i,水能到达的最大高度等于下标 i 两边的最大高度的最小值,下标 i 处能接的水的量等于下标 i 处的水能到达的 最大高度减去 height[i]
- 存放在leftmax,rightmax里面
- 对于每个来说其位置存水量就是
min(leftmax[i],rightmax[i])-height[i]
class Solution {
public int trap(int[] height) {
int len = height.length;
if(len==0) {
return 0;
}
int[] leftmax=new int[len]; //用来记录元素左边最大值
leftmax[0]=height[0];
int[] rightmax=new int[len]; //用于记录元素右边最大值
rightmax[len-1]=height[len-1];
for (int i = 1; i < len; i++) {
leftmax[i]=Math.max(leftmax[i-1], height[i]);
}
for (int i = len-2; i >= 0; i--) {
rightmax[i]=Math.max(rightmax[i+1], height[i]);
}
int sum=0;
for (int i = 0; i < len; i++) {
int temp=Math.min(leftmax[i], rightmax[i]);
sum += (temp-height[i]);
}
return sum;
}
}
复杂度分析
时间复杂度:O(n),其中 n 是数组 height 的长度。计算数组leftmax,rightmax的元素值各需要遍历数组height 一次,计算能接的水的总量还需要遍历一次。
空间复杂度:O(n),其中 n 是数组height 的长度。需要创建两个长度为 n 的数组leftmax,rightmax
解法三:双指针
可以优化的动态规划,就是把leftmax[],和rightmax[]变成left,right指针
class Solution {
public int trap(int[] height) {
int ans = 0;
int left = 0, right = height.length - 1;
int leftMax = 0, rightMax = 0;
while (left < right) {
leftMax = Math.max(leftMax, height[left]);
rightMax = Math.max(rightMax, height[right]);
if (height[left] < height[right]) {
ans += leftMax - height[left];
++left;
} else {
ans += rightMax - height[right];
--right;
}
}
return ans;
}
}
复杂度分析
时间复杂度:O(n),其中 n 是数组 height 的长度。两个指针的移动总次数不超过 n。空间复杂度:O(1)。只需要使用常数的额外空间。