42. 接雨水

42. 接雨水

题目描述

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例1:
在这里插入图片描述

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 

示例2:

输入:height = [4,2,0,3,2,5]
输出:9

提示:

  • n = = h e i g h t . l e n g t h n == height.length n==height.length
  • 0 ≤ n ≤ 3 ∗ 1 0 4 0 \le n \le 3 * 10^4 0n3104
  • 0 ≤ h e i g h t [ i ] ≤ 105 0 \le height[i] \le 105 0height[i]105

题解:

这题可以从两个方向考虑,一种是竖着,一种是横着。

法一(竖着):

只考虑 i 位置上方能放几格水。那么很容易想到 i 位置上方水的数量的表达式:max{min{ i 左侧的最大值, i 右侧的最大值} - height[i], 0}

对所有位置的结果累加求和就是最终结果。然后再求上述表达式的结果时,就有一些值得探究的地方了。

  • 最基础的解法就是暴力了,对于每个位置 i ,用两个循环求 [0...i-1] 的最大值和 [i+1,n-1] 的最大值。但是这题数据肯定过不去,代码就不贴了。

  • 其实可以不用那么暴力,我们可以额外使用两个数组 leftMaxrightMaxleftMax[i] 表示 [0...i] 的最大值,right[i] 表示 [i...n-1] 的最大值。从左往右遍历一遍,生成 leftMax;从右往左一遍,生成 rightMax。代码如下:

    class Solution {
    public:
        int trap(vector<int>& height) {
            int n = height.size();
            if ( n < 3 ) return 0;
            vector<int> leftMax( n ), rightMax( n );
            leftMax[0] = height[0];
            for ( int i = 1; i < n; ++i )
                leftMax[i] = max( leftMax[i - 1], height[i] );
            rightMax[n - 1] = height[n - 1];
            for ( int i = n - 2; i >= 0; --i )
                rightMax[i] = max( rightMax[i + 1], height[i] );
            int ret = 0;
            for ( int i = 1; i < n - 1; ++i )
                ret += max( min( leftMax[i - 1],rightMax[i + 1] ) - height[i], 0 );
            return ret;
        }
    };
    /*
    时间:8ms,击败:90.86%
    内存:14.1MB,击败:90.60%
    */
    
  • 其实还可以使用双指针,做到额外空间复杂度 O ( 1 ) O(1) O(1) 。具体就是:使用两个指针 lr ,开始分别指向 0n - 1 ,两个变量 leftMaxrightMax ,分别表示 l 左边的最大值和 r 右边的最大值,开始时为 arr[0]arr[n - 1],每一步让 l 移动或者 r 移动,具体分情况讨论:

    在这里插入图片描述

    1. leftMax <= rightMax ,此时可以求出 l 位置上方的水量。因为 rightMaxarr[r + 1...n - 1] 的最大值,而 l 的右侧还有一个未知区域,所以 l 右侧最大值一定不会小于 rightMaxleftMax 代表 l 左侧的最大值,因为 leftMax <= rightMax ,可知 leftMaxl 位置的瓶颈。故 l 位置上方的水量为 max{leftMax - arr[l], 0} ,然后让 l 向右移动,并且移动前需要更新 leftMax = max{leftMax, arr[l]}
    2. leftMax > rightMax ,分析同 1。

    代码如下:

    class Solution {
    public:
        int trap(vector<int>& height) {
            int n = height.size();
            if ( n < 3 ) return 0;
            int lmax = height[0], rmax = height[n - 1];
            int ret = 0, l = 1, r = n - 2;
            while ( l <= r ) {
                if ( lmax <= rmax ) {
                    ret += max( lmax - height[l], 0 );
                    lmax = max( lmax, height[l++] );
                } else {
                    ret += max( rmax - height[r], 0 );
                    rmax = max( rmax, height[r--] );
                }
            }
            return ret;
        }
    };
    /*
    时间:0ms,击败:100.00%
    内存:13.8MB,击败:95.18%
    */
    
法二(横着):

就是考虑一个空格往左往右最长能延长到什么地方(按层的意思)。

我们可以观察到,当柱子之间形成一个 u 形槽时,就会积累雨水。我们维护一个栈底到栈顶严格递减的单调栈,如果 arr[i] >= arr[stk.top()] ,说明此时形成了 凹槽 ,将栈顶元素弹出,记为 top ,此时增加的雨水量是:(i - stk.top() - 1) * (min(arr[i], arr[stk.top()]) - arr[top])

法二代码:

class Solution {
public:
    int trap(vector<int>& height) {
        int n = height.size();
        if ( n < 3 ) return 0;
        vector<int> stk;
        int top, ret = 0;
        for ( int i = 0; i < height.size(); ++i ) {
            while ( stk.size() && height[stk.back()] <= height[i] ) {
                top = stk.back();
                stk.pop_back();
                if ( !stk.size() ) break;
                ret += ( i - stk.back() - 1 ) * (min( height[i], height[stk.back()] ) - height[top]);
            }
            stk.push_back( i );
        }
        return ret;
    }
};
/*
时间:8ms,击败:90.86%
内存:13.9MB,击败:94.12%
*/
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值