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!
和前面一题Container with Most Water是非常类似的题目。但是这题并非找两个边界使其面积最大,而是要求所有雨水的面积,所以还是很大区别的。
有多种解法,一种是基于栈的,另外一种两次扫描,获取每个柱子的左右两边最高的柱子,而每根柱子可以容纳的雨水就是min(max_left,max_right)。这是一次只算一根柱子的做法。栈的做法是每次横向算一个level(或多个level)的存储水量。
两次扫描的代码很好理解,我的实现如下:
class Solution(object): def trap(self, height): """ :type height: List[int] :rtype: int """ if not height or len(height) < 3: return 0 n = len(height) left = [height[0]] right = [height[-1]] #一次完成左右最高值的扫描 for i in xrange(1,n): if height[i-1] > left[-1]: left.append(height[i-1]) else: left.append(left[-1]) if height[n-i] > right[-1]: right.append(height[n-i]) else: right.append(right[-1]) water = 0
#计算面积 for i in xrange(1,n-1): res = min(left[i],right[~i]) - height[i] if res > 0: water += res return water
上述做法的时间复杂度为O(n),空间复杂度也为O(n),这种做法可以使用双指针使其空间复杂度降为O(1),是一个最优解,其做法是保存左边的一个最高值和右边的一个最高值,同时用left和right双指针来确定当前要处理的bar。当左边的最高值小于右边最高值时,且left指针所指向的bar低于左边最高值时,说明对于left的这个bar,我们已经找到了它左边和右边最高值的比较小的值:leftright,可以确定这个bar上可以灌的水。当右边的最高值高于左边的最高值时,同理可以操作。
灌水类题的真谛是,bar左边的最高值,右边的最高值和bar本身奠定了这个bar的灌水基调。
代码如下:
class Solution: # @param heights: a list of integers # @return: a integer def trapRainWater(self, heights): #two pointer solution, also find every bar's left highest and right highest #not first so the area is the vertical area of each bar. if not heights or len(heights) < 3: return 0 left = 0 right = len(heights) - 1 area = 0 ans = [0]*len(heights) leftheight = heights[0] rightheight = heights[right] while left < right - 1: if leftheight < rightheight: left += 1 print 'left:',left if leftheight > heights[left]: area += leftheight - heights[left] else: leftheight = heights[left] else: right -= 1 if rightheight > heights[right]: area += rightheight - heights[right] else: rightheight = heights[right] return area
基于栈的做法,是使用栈计算一个递减序列,对于栈顶元素来说,栈内倒数第二个元素都比其高,一旦当前遍历到的元素比栈顶要高,则可以计算栈顶这个bar为bottom可以存储的横向水量。所有元素都会执行一次入栈。代码如下:
if not height or len(height) < 3: return 0 area = 0 #decreasing stack , while increasing also pop not calculate, because it was calculated by the next equal one #if the decresing stack still contain elements when meet end, leave them there , they cant't trap water stack = [] for i in xrange(len(height)): while stack and height[i] >= height[stack[-1]]: bottom = height[stack.pop()] if stack: bound = min(height[stack[-1]],height[i]) h = bound - bottom w = i - stack[-1] -1 area += h*w stack.append(i) return area
这题是使用单调递减栈来找到每个bar左边第一个比它高的bar,和右边第一个比它高的bar。每次要出栈一个元素时,就需要计算以当前bar高度为最低水位可以填充的水(注意这里是横向水量),最多可以填充的量是,(min(height[left_first], height[right_first]) - height[bar]) * (right_first - left_first)。 注意的情况是 一旦一个bar被填充了水之后,它的高度就变成了min(height[left_fist], height[right_first]). 左边或者右边还没处理的bar可以忽略这个bar的存在,继续添水。另外单调栈涉及到一个最后是否要清空栈的问题, 但是这题在最后,如果都是递减的情况,存不住水, 所以不需要压入-1来处理。