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!

和前面一题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来处理。

转载于:https://www.cnblogs.com/sherylwang/p/5540649.html

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值