算法分析与设计,第四周博客
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^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;
}
}