直方图蓄水问题 Trapping Rain Water

142 篇文章 20 订阅
51 篇文章 0 订阅

问题: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.

思路:

解法一:

以某一个柱子为例,其上面能否蓄水,取决于其左侧柱子最大高度、右侧柱子最大高度的较小值与该柱子高度的差值。如果差值是正的,则其上方可以蓄水;否则不可以。于是问题就转换为,如何快速求得一个柱子左右两侧的最大高度。

借助两个辅助数组来完成吧。数组LM,其元素LM[i]记录0~i-1号元素中的最大值。数组RM,其元素RM[i]记录i+1~n-1号元素中的最大值。这两个数组只要左右各遍历一遍即可求得。

该方法的时间复杂度O(N),空间复杂度O(N)。

class Solution {
public:
    int min(int a, int b)
    {
        return a>b?b:a;
    }
    
    int trap(int A[], int n) {
        if(n <= 1)
            return 0;
        int water = 0;
        int *LM = new int[n];
        int *RM = new int[n];
        //LM记录每个柱子左侧的最大高度
        LM[0] = A[0];
        for(int i=1;i<n;i++)
            if(A[i] > LM[i-1])
                LM[i] = A[i];
            else
                LM[i] = LM[i-1];
        //RM记录每个柱子右侧的最大高度
        RM[n-1] = A[n-1];
        for(int i=n-2;i>=0;i--)
            if(A[i] > RM[i+1])
                RM[i] = A[i];
            else
                RM[i] = RM[i+1];
        //计算每个柱子上面能存放的水量
        for(int i=1;i<n-1;i++)
        {
            int k = min(LM[i], RM[i]);
            if(k > A[i])
                water += k - A[i];
        }
        delete []LM;
        delete []RM;
        return water;
    }
};

解法二:

避免使用辅助空间,先找到数组中的最高点。该点将数组分为左右两半。对于左半侧,从左向右处理;对于右半侧,从右向左处理。处理的时候只需考虑已扫描一侧的高度即可,因另一侧有数组最高点,无需担心蓄水问题。

该方法的时间复杂度O(N),空间复杂度O(1)。

class Solution {
public:
    int trap(int A[], int n) {
        
        int max = 0;
        for(int i=1;i<n;i++)
            if(A[i] > A[max])
                max = i;

        int water = 0;
        for(int i=1, top=0;i<max;i++)
            if(A[i] > A[top])
                top = i;
            else
                water += A[top] - A[i];
            
        for(int i=n-2, top=n-1;i>max;i--)
            if(A[i] > A[top])
                top = i;
            else
                water += A[top] - A[i];
                
        return water;
    }
};


解法三:

这道题让我想到了另一道题,《寻找直方图中的最大矩形》。那道题的用栈来维护一个有序序列的方法可以借鉴过来。

顺序取数组元素,同时维护一个栈(存的是下标),该栈只保存降序:

当栈为空时,可以直接将数组元素i加入栈中。

当栈不为空时,如果数组元素i比栈顶对应元素小,则继续压栈。

当栈不为空时,如果数组元素i比栈顶对应元素大,则要栈顶出栈,计算其蓄水能力(比较其与新栈顶和数组元素i的大小关系)。

该方法的时间复杂度O(N),空间复杂度O(N)。

class Solution {
public:
    int min(int a, int b)
    {
        return a>b?b:a;
    }
    int trap(int A[], int n) {
        if(n <= 1)
            return 0;
        int water = 0;

        stack<int> s;
        for(int i=0;i<n;i++)
        {
            if(s.empty())
                s.push(i);
            else
            {
                if(A[i] <= A[s.top()]) // 若小于栈顶,则入栈
                    s.push(i);
                else 
                {
                    while(!s.empty() && A[i] > A[s.top()]) //若大于栈顶,则清算
                    {
                        int k = s.top();
                        s.pop();
                        if(!s.empty())
                            water += (min(A[s.top()],A[i]) - A[k])*(i-s.top()-1);
                    }
                    s.push(i);
                }
            }
        }
        return water;
    }
};

注意解法二中,计算一次蓄水量时不一定就是某柱子上方所有的水量。而只是这一层次的。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值