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.

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!

Example:

Input: [0,1,0,2,1,0,1,3,2,1,2,1]
Output: 6

Leetcode 官方总结: https://leetcode.com/articles/trapping-rain-water/

方法0: brute force

双重循环

Complexity

Time complexity: O(n^2)
Space complexity: O(1)

方法1:

basketking:https://www.youtube.com/watch?v=8BHqSdwyODs
grandyang:http://www.cnblogs.com/grandyang/p/4402392.html

思路:

主要的破题点是:当一个位置左边最高点和右边最高点确定,该位置的存雨量就能确定。所以关键是能否在尽量少的遍历中确定左右最高点。

第一种方法首先遍历两遍数组,找到每一个位置所对应的leftMost 和 rightMost,存成两个vector。然后对于每个点就肯定能求出存雨量,公式是:

rain[i] = min(leftMost[i], rightMost[i]) - height[i]

再遍历一遍或者在第二遍遍历中就能累加出结果。

易错点:

  1. 每次统计左高和右高不涉及当前点的高度,i.e. leftMost[i] = max(leftMost[i - 1], height[i - 1])
  2. 最左边的左高点是0,最右边的右高点是0.因此这两点可以确定存雨量为0,可以用来初始化并从各自的下一个位置开始遍历
  3. 每个位置的存雨量不会是负数。当前高度如果大于左高和右高,不应该进行累计

Complexity

Time complexity: O(n)
Space complexity: O(n)

class Solution {
public:
    int trap(vector<int>& height) {
        int n = height.size();
        vector<int> leftMost(n, 0);
        vector<int> rightMost(n, 0);
        int result = 0;
        for (int i = 1; i < n; i++){
            leftMost[i] = max(leftMost[i - 1], height[i - 1]); 
        }
        for (int j = n - 2; j >=0; j--){
            rightMost[j] = max(rightMost[j + 1], height[j + 1]);
            if ( min(rightMost[j], leftMost[j]) - height[j] > 0) {
                result += min(rightMost[j], leftMost[j]) - height[j]; 
            }
        }
        return result;        
    }
};

方法2: two pointers

思路:

上面的方法进行化简,以达到一次遍历就确定每一点的存雨量。采取了篮子王的思路:双指针从两边向中间遍历(recall 11. Container With Most Water,但是又不一样,这道题不关心中间的高点)。这个过程中每次对leftMost 和rightMost 进行比较,对较小方向的对应指针而言,该点的雨水值就一斤确定了。对于左右指针,雨量高度都不会低于左右较小值,但是如果中间还有高点,高点较小的一边不会受影响,而较高的一边会被中间拉高。因此每次累计可以确定的一边雨量,并且移动相应指针。

易错点

  1. 和上面的做法一样左右边界需要注意
  2. 和上面一样不应该累计负雨量
  3. 终止条件必须是left <= right,因为有corner case,起始值就会出现left = right: [2, 0, 2]

Complexity

Time complexity: O(n)
Space complexity: O(1)

class Solution {
public:
    int trap(vector<int>& height) {
        int n = height.size();
        int leftmost = 0;
        int rightmost =0;
        int result = 0;
        int left = 1, right = n - 2;
        while (left <= right){
            leftmost = max(leftmost, height[left - 1]);
            rightmost = max(rightmost, height[right + 1]);
            if (leftmost <= rightmost){
                result += leftmost - height[left] > 0 ? leftmost - height[left] : 0;
                left ++;
            }
            else {
                result += rightmost -height[right] > 0 ? rightmost - height[right] : 0;
                right --;
            }
        }      
        return result;        
    }
};

方法3: monotonic stack

这道题还有stack的做法,二刷请实现。
stack 在对result的累加上应该是以整块面积为单位,而不是每一栋楼的存雨量
grandyang:http://www.cnblogs.com/grandyang/p/4402392.html

思路:

和84题很像,也是维持一个单调递减栈,而每一次栈顶的元素是已经遇到左右高点的一个坑,也是计算面积/雨水平面的决定因素。我们取出来这个元素记住他的高度,然后用此时栈顶元素和当前i确定宽度,加入结果。此时处理栈空的方法不一样:84题即使没有左高点,也要记录一次area,但是trapping water里的水会全部流走,直接continue。这样计算后,i暂时不入栈,也不前进,继续和下一个栈顶比较高度,它可能会使栈内几个元素连续成坑。而每一次pop掉坑,提取左边届,直到为空或者单调递减条件满足,i才会入栈。此时i成为最低点,尚且没有遇到它的右高点,无法确定面积,所以继续向前。

Complexity

Time complexity: O(n)
Space complexity: O(n)

class Solution {
public:
    int trap(vector<int>& height) {
        stack<int> st;
        int i = 0, n = height.size(), res = 0;
        while (i < n) {
            if (st.empty() || height[st.top()] > height[i]) {
                st.push(i++);
            }
            else {
                int horizon = height[st.top()];
                st.pop();
                if (st.empty()) continue;
                res += (min(height[st.top()], height[i]) - horizon) * (i - st.top() - 1);
            }
        }
        return res;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值