LeetCode-Trapping Rain Water

来源:互联网 发布:两组数据的对比 编辑:程序博客网 时间:2024/06/05 15:53

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

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;elsei = 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),而且是一个稳定的算法。对比前后代码的所用时间,后者确实比前者要少。说明这个算法的改进是有效的。


原创粉丝点击