LeetCode-Trapping Rain Water

算法分析与设计,第四周博客

42. Trapping Rain Water

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!

本题读完描述以后可能感觉不是很清楚,但是看到相关的配图以后就有一种豁然开朗的感觉。总的来说,就是给你一个数组height,数组中的第i项代表着一个宽为1的、高为height[i]的方块,然后这些方块排列在i轴上,求这些方块和i轴组成的整个部分能容纳多少水。

这题的解题思路有很多种啦,各位有兴趣可以去Solution那里查看。在这里,我就介绍下我所用的方法。

我所用的方法是按照题目的意思分析得到的直接解法,也就是基本上只适用与这一道题目。算法的时间复杂度是O(n),空间复杂度是O(1)。如果分要说,这个方法是属于什么类型的,我想大概是属于指针类吧。

整体思路是,从头开始搜寻,直到第一个非零的数,即找到第一个符合条件的i,使得height[i] != 0,这样做的原因很清楚,因为两边是没有边界的,所以头部和尾部为零的部分是不能夠储水的,所以把它们去掉。

然后,从i+1开始,寻找第一个不小于height[i] 的数j,并记录下期间所有小于height[i]的数的和more。这么做的理由也很简单,水只能存在于几个有高度差之间的方块中,那么我们已经找到可以储水的地方了,该如何计算这之间的储水量呢?如果我们只看开始的方块和结束的方块,那么显然储水量 s = (j-i-1)*height[i],但是这中间有其他的方块存在,它们占据了一定的空间,而这个空间正好是more,这也就是为什么需要more的原因。所以,储水量 s = (j-i-1)*height[i] - more;

然后,令i = j,继续刚才的操作,直到遍历到最后。但是,这就完了吗?

没有,我们还有一种情况没有处理,那就是,当i指到最高的那个方块的时候,我们并不能找出比它更高的方块了,那么,该怎么办呢?我的方法是,把它变成它之后的方块中最高的那个,也就是 height[i] = max(height[i+1], ..., height[n-1]);这样我们至少就能找到和它一样高的方块了,然后重复上面的步骤,而且,这并不会对结果造成任何影响,却又解决了这个问题。

所有的步骤就是这些了,下面是代码:

public class Solution {
	int sum = 0;
	int find(int[] height, int i) {
		int n = height.length;
		while (i < n && height[i] == 0)
			++i;
        if (i >= n)
			return -1;
		int shorter = i;
		++i;
		int more = 0;
		int max = 0;
		while (i < n && height[i] < height[shorter])
		{
			more += height[i];
			if (height[i] > max)
				max = height[i];
			++i;
		}
		if (i >= n)
		{
			height[shorter] = max;
			return find(height, shorter);
		}
		sum += (i-shorter-1)*height[shorter]-more;
		return i;
	}
	
	public int trap(int[] height) {
		int n = height.length;
		int i = 0;
		while (i < n) {
			int next = find(height, i);
			if (next == -1)
				++i;
			else
				i = next;
		}
		return sum;
    }
	
	public static void main(String[] args) {
		Solution solution = new Solution();
		int[] h =  {0,1,0,2,1,0,1,3,2,1,2,1};
		System.out.println(solution.trap(h));
	}
}

可以看到,这个算法的平均时间复杂度是O(n),但是在最坏的情况下(数组高度是降序排列的)时间复杂度会变成O(n^2),因为当遇到局部最高峰时需要从当前位置遍历到尾部,并且需要再次递归;但空间复杂度度确实是O(1)。

那既然这个算法的最坏时间复杂度是O(n^2),那有没有什么方法可以避免呢,或者说,这个算法,可以在那些地方进行改进。回顾上面那个算法,在到达了局部最高点时,它的复杂度就提上来了,因为它需要在进行一次调用,那么该怎么样省去这个调用呢?方法就是从首尾两端,进行计算。

总体的思路和上面的方法一致,但是,这次我们同时从首尾两端开始,让两个指针不断的靠近,当遇到最高点时,两者就相遇了,此时停止,得到结果。所实现的代码如下:

public class Solution {
	int sum = 0;
	int find(int[] height, int i, int direction) {
		int n = height.length;
		while (i < n && i >= 0 &&  height[i] == 0)
			i += direction;
        if (i >= n || i < 0)
			return -1;
		int shorter = i;
		i += direction;
		int more = 0;
		int max = 0;
		while (i < n && i >= 0 && height[i] < height[shorter])
		{
			more += height[i];
			if (height[i] > max)
				max = height[i];
			i += direction;
		}
        if (i >= n || i < 0)
		{
			return shorter;
		}
		sum += (Math.abs(i-shorter)-1)*height[shorter]-more;
		return i;
	}
	
	
	public int trap(int[] height) {
		int n = height.length;
		int i = 0;
		int j = n-1;
		while (i < n && j >= 0) {
			int next_i = find(height, i, 1);
			i = next_i;
			if (i == j)
				break;
			int next_j = find(height, j, -1);
			j = next_j;
			if (i == j)
				break;
		}
		
		return sum;
    }
}


改进后的算法只需要遍历一次,时间复杂度确实是O(n),而且是一个稳定的算法。对比前后代码的所用时间,后者确实比前者要少。说明这个算法的改进是有效的。


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值