Leetcode.面试题17.21. 直方图的水量

一、题目

给定一个直方图(也称柱状图),假设有人从上面源源不断地倒水,最后直方图能存多少水量?直方图的宽度为 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)

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值