leetcode_42 接雨水

1. 题意

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

2. 题解

这个题不会做,全部是看得题解捏。

不过能看懂题解感觉自己也很棒了!

看完题解后感觉最难的是如何求出有多少积水,

答案直接给的是当前位置的积水量是它左右两边两个最大值的最小值

减去当前的高度。

灵茶山艾府的题解中解释了为什么?如果比两端最高的高积水肯定会

从一侧流出去。

不过我肯定是想不到的。

有了这个结论之后,其实就可以做一下了。

对于每个高度,求出它左右两侧的最高高度,从而计算积水的高度。

2.1 前后缀最大值

算出前后缀的最高高度。

再遍历一次计算积水量。

cpp 复制代码
class Solution {
public:
    int trap(vector<int>& height) {
        
        int n = height.size();
        int ans = 0;


        vector<int> left_max(n, 0);
        vector<int> right_max(n, 0);

        for (int i = 1; i < n; ++i) {
            left_max[i] = max(left_max[i - 1], height[i - 1]);
        }

        for (int i = n - 2; ~i; --i) {
            right_max[i] = max(right_max[i + 1], height[i + 1]);
        }

        for (int i = 1; i < n - 1; ++i) {
            ans += max(0, min(left_max[i], right_max[i]) - height[i] );
        }

        return ans;
    }
};

当然可以稍微优化下空间,不过差别不大。

cpp 复制代码
class Solution {
public:
    int trap(vector<int>& height) {
        
        int n = height.size();
        int ans = 0;
        
        vector<int> rightMx(n + 1, -1);

        for (int i = n - 1; ~i; --i) {
            rightMx[i] = max(height[i], rightMx[i + 1]);
        }

        int leftMx = height[0];
        for (int i = 1; i < n - 1; ++i) {
          ans += max( 0, min(leftMx, rightMx[i + 1]) - height[i]);
          leftMx = max(height[i], leftMx);
        }



        return ans;
    }
};

时间复杂度 O ( n ) O(n) O(n)

空间复杂度 O ( n ) O(n) O(n)

2.2 双指针

双指针的解法确实比较巧妙,先说下做法,再证明为什么正确,最后给出代码。

思路是,左指针 l l l指向左端,右指针 r r r指向右端,同时维护两个变量:

l e f t M a x leftMax leftMax表示 l l l左端的最大值,

r i g h t M a x rightMax rightMax表示 r r r右端的最大值。

当 l e f t M a x < r i g h t M a x leftMax <rightMax leftMax<rightMax时,

左指针元素积水高度为 l e f t M a x − h e i g h t [ l ] leftMax -height[l] leftMax−height[l],左指针右移。

当 l e f t M a x ≥ r i g h t M a x leftMax \ge rightMax leftMax≥rightMax时,

右指针元素积水高度为 r i g h t M a x − h e i g h t [ r ] rightMax - height[r] rightMax−height[r],右指针左移。

为什么这个策略正确呢? 下面简要证明。

我们令 l m x ( i ) = max ⁡ { h e i g h t [ k ] , 0 ≤ k ≤ i } lmx(i) = \max\{height[k] , 0 \le k \le i\} lmx(i)=max{height[k],0≤k≤i},

也就是 l m x ( i ) lmx(i) lmx(i)表示下标 i i i左端的最大值。

我们令 r m x ( i ) = max ⁡ { h e i g h t [ k ] , i ≤ k ≤ n − 1 } rmx(i)=\max\{ height[k],i \le k \le n-1\} rmx(i)=max{height[k],i≤k≤n−1},

也就是 r m x ( i ) rmx(i) rmx(i)表示下标 i i i右端的最大值。

我们令 w [ i ] w[i] w[i]表示位置 i i i的积水高度,

那么 w [ i ] = min ⁡ ( l m x ( i ) , r m x ( i ) ) − h e i g h t [ i ] w[i] =\min(lmx(i),rmx(i))-height[i] w[i]=min(lmx(i),rmx(i))−height[i]。

当 l ≤ r l \le r l≤r时,我们很容易得到

l m x ( l ) ≤ l m x ( r ) r m x ( l ) ≥ r m x ( r ) lmx(l) \le lmx(r)\\ rmx(l) \ge rmx(r) lmx(l)≤lmx(r)rmx(l)≥rmx(r)

上面的 l e f t M a x leftMax leftMax相当于 l m x ( l ) lmx(l) lmx(l), r i g h t M a x rightMax rightMax相当于 r m x ( r ) rmx(r) rmx(r)。

当 l m x ( l ) < r m x ( r ) lmx(l) < rmx(r) lmx(l)<rmx(r)时,必然有 l m x ( l ) < r m x ( r ) ≤ r m x ( l ) lmx(l) < rmx(r) \le rmx(l) lmx(l)<rmx(r)≤rmx(l),

因此 min ⁡ ( l m x ( l ) , r m x ( l ) ) = l m x ( l ) \min(lmx(l), rmx(l))=lmx(l) min(lmx(l),rmx(l))=lmx(l)。

同理 l m x ( l ) ≥ r m x ( r ) lmx(l) \ge rmx(r) lmx(l)≥rmx(r)时,必然有 r m x ( r ) ≤ l m x ( l ) ≤ l m x ( r ) rmx(r) \le lmx(l) \le lmx(r) rmx(r)≤lmx(l)≤lmx(r),

因此 min ⁡ ( l m x ( r ) , r m x ( r ) ) = r m x ( r ) \min( lmx(r), rmx(r)) = rmx(r) min(lmx(r),rmx(r))=rmx(r)。

综上,这个策略是正确的。

下面给出不那么重要的代码:

cpp 复制代码
class Solution {
public:
    int trap(vector<int>& height) {
        
        int n = height.size();
        int ans = 0;

        int l = 0;
        int r = n - 1;
        int leftMax = 0;
        int rightMax = 0;

        while (l <= r) {

            leftMax = max( leftMax, height[l]);
            rightMax = max( rightMax, height[r]);

            if ( height[l] < height[r]) {
                ans += leftMax - height[l];
                ++l;
            }
            else {
                ans += rightMax - height[r];
                --r;
            }
        }


        return ans;
    }
};

时间复杂度 O ( n ) O(n) O(n)

空间复杂度 O ( 1 ) O(1) O(1)

2.3 单调栈

说实话不是很明白单调栈的做法怎么想到的,

它比前后缀的做法要难理解的多,也不知道怎么来的,纯纯为了

用一下这个数据结构?

核心思想是逐步填充积水高度。

当栈顶元素大于新元素时,直接将新元素入栈。

否则,将栈顶元素出栈,令为 k k k,

将积水高度填充到与新栈顶 l e f t left left或新元素 h e i g h t [ i ] height[i] height[i]的最小值,

即 min ⁡ ( h e i g h t [ i ] , h e i g h t [ l e f t ] ) − h e i g h t [ k ] \min(height[i], height[left]) -height[k] min(height[i],height[left])−height[k]

填充的宽度就是 i − l e f t − 1 i-left-1 i−left−1

不断重复这个过程直到栈空或者栈顶大于新元素,再将新元素入栈。

叙述起来很复杂,还是看下图吧,像个一步步填坑的过程。



下面给出代码

cpp 复制代码
class Solution {
public:
    int trap(vector<int>& height) {
        
        int n = height.size();
        stack<int> s;

        int ans = 0;
        for (int i = 0; i < n; ++i) { 

            while (!s.empty() && height[s.top()] <= height[i]) {
                int k = s.top();
                s.pop();

                if (!s.empty()) {
                    int left = s.top();

                    ans += ( i - left - 1 ) * (min(height[i], height[left]) - height[k] );
                }
            }


            
            s.push( i );
        }


        return ans;
    }
};

时间复杂度 O ( n ) O(n) O(n),空间复杂度 O ( n ) O(n) O(n)

3. 参考

leetcode
0x3f

相关推荐
3***g205几秒前
MATLAB高效算法设计原则利用MATLAB内置函数
开发语言·算法·matlab
雍凉明月夜1 分钟前
深度学习之目标检测yolo算法Ⅰ(v1-v3)
深度学习·算法·yolo·目标检测
s09071361 分钟前
【计算机视觉】详解立体匹配算法:原理、公式与核心策略
人工智能·算法·计算机视觉·立体匹配
MicroTech20257 分钟前
MLGO微算法科技通过高阶方法和重新缩放进一步改进非线性微分方程的量子算法
科技·算法
菜鸟233号14 分钟前
力扣474 一和零 java实现
java·数据结构·算法·leetcode·动态规划
CoovallyAIHub18 分钟前
2026 CES 如何用“视觉”改变生活?机器的“视觉大脑”被点亮
深度学习·算法·计算机视觉
abbiz18 分钟前
30 个 Android 面试高频问题及答案
android·面试·职场和发展
小杨同学4918 分钟前
C 语言实战:水果总价计算程序(结构体应用 + 细节优化)
后端·算法·程序员
2301_8002561127 分钟前
【人工智能引论期末复习】第4章 机器学习1-基础知识
人工智能·算法·机器学习
seeksky28 分钟前
分词与 BPE 实现(tiktoken)
算法