21 力扣热题刷题记录之第42题接雨水

系列文章目录

力扣刷题记录


前言

每天进步一点点!!

好像变懒了,不太愿意记录自己的刷题过程,因为觉得很浪费时间,要自己去总结,去对比,都没时间干题目了。但是转念一想,如果这个题目没有充分搞明白,不会举一反三,那么继续刷题下去的意义是什么,遇到类似的还是不会写,倒不如慢慢的,搞懂,搞透,急什么。毕竟有人它是下笔必得分,有人它是这里一点那里一点,无意义罢了。

一、背景

在这里插入图片描述

链接:https://leetcode-cn.com/problems/trapping-rain-water/
来源:力扣(LeetCode)

二、我的思路

本质上说,这不是我的思路。此前偶然看过一个视频是讲这个接雨水的问题的,那个人的思路我恰巧记住了,但是实现起来还是比较费劲,其实就是代码敲得少,不熟悉,各种bug,边界考虑不全。

思路是这样的:

雨水怎么样才能被存储起来呢?就是说,有凹槽。这里把两边的高度,称作为“木板”吧。于是我想到,有凹槽的话,那对应数组值不就是先下降,后上升,找到这个凹槽就可以开始记录水容量了。

这里注意一个问题,下降和上升都不是严格意义的,而是说单调不增和单调不减,简单来说,平的也算嘛。

好了,这样似乎一个循环就可以解决,真美好。可是遇到一个问题,这个凹槽是拘泥于左右两侧的板子决定的储水量,若是后序有更高的板子,那这部分没有考虑到呀。

这可如何是好?

艾,如果有更高的,加上更多的那部分水量,就是用当前可用的板子高度-原先计算储水的高度,再乘以一个宽度,这样就可以计算了呀。可是原来板子的高度用什么记录,每一次计算的时候,新板子的高度都要和原来的板子高度比较嘛?如果板子高度不是马上体现出来呢(比如左侧最大高度为100,只有遇到右侧最大高度接近100的时候,比如99,101这样子,它才会有意义),这个时候就必须重新计算。

我想的办法就是,维护一个数组,记录每个凹槽的局部储水量,并把它的高度存储起来,然后重新遇到要更新之前储水的时候,如果新板子的高度大于当前元素,那么更新,如果不大于,就加上原来凹槽的高度。

好吧,直接来代码,大家可能也不知道我在说啥:

class Solution {
public:
    int trap(vector<int>& height) {
        /*
        *思路:先减后增的数据可以存放雨水
        *每次找到左右边界即可,左右边界中选取较小的那个值作为桶高
        */
        //存放下标,只记录左中右元素来区分是否属于两边高,中间低的情况
        vector<int > my_sta;//只放三个元素
        int d[20001]={0};
        int p[20001]={0};
        int area=0;
        bool reduce=false;//标志变量,表示开始下降
        bool increase=false;

        int start=0;//记录开始存水的位置,就是开始下降的位置
        int h=INT_MAX;//记录高度

        for (int i=0;i<height.size();i++)
        {
            if (!reduce && height[i]>0 && i<height.size()-1 &&height[i]>height[i+1])
            {
                reduce=true;//表示开始下降
                h=min(h,height[i]);
                start=i;
                my_sta.push_back(start);
                //快速下降
                while ( i<height.size()-1 && height[i]>=height[i+1])
                {
                    i++;
                }
            }
            if (reduce && !increase && i>0 && height[i]>height[i-1] )
            {
                increase=true;
                //快速上升
                while ( i<height.size()-1 && height[i+1]>=height[i])
                {
                    i++;
                }
                h=min(h,height[i]);
                
            }
            if (reduce && increase)
            {
                //reduce=false;//归位
                increase=false;
                //计算之间的总面积,不含边界
                area+=h*(i-start-1);
                p[i]=h;//表示边界前是用什么高度计算的
                //减去多计算的面积
                for (int j=i-1;j>start; j--)
                {
                    //数值比高还高,说明装不了水
                    if (height[j]>=h)
                    {
                        area-=h;
                    }
                    else 
                        area-=height[j];
                }
                if (my_sta.size()>=2)
                {
                    if (height[my_sta[1]]<height[i] && height[my_sta[1]] < height[my_sta[0]])
                    {
                        area=d[my_sta[0]];//从头计算
                        int tmp=0;
                        int he=0;
                        he=min(height[i],height[my_sta[0]]);
                        p[i]=he;//表示边界前是用什么高度计算的
                        for (int j=i-1;j>my_sta[0]; j--)
                        {
                            //如果当前用的边界值不如原来使用的
                            if (he<=p[j])
                            {
                                area=d[j];
                                break;
                            }
                            else 
                                tmp+= (he- height[j])>0 ? (he- height[j]):0;
                        } 
                        area+=tmp;
                    }
                    my_sta.pop_back();//删除中间元素
                    
                    
                }
                h=height[i];//左边界变为当前元素
                start=i;//就是开始下降的边界
                //如果左边界不是最高,更换左边界
                if(my_sta.size()>0 && height[i] >= height[my_sta[0]])
                {
                    my_sta.clear();
                }
                d[i]=area;//存放以右边界为止的雨水面积
                my_sta.push_back(i);//存放右边界  
            }
        }
        return area;
    }
};

这么复杂的代码,恐怕只有我自己能看懂了吧,这不是一个好的思路。大家别看了。

三、官方的思路

1.确定当前元素左右最大的板子 + 优化

考虑存水与否,看的是两边的板子哪个短,就是当前储水的高度值,这个很简单。

关于确定两边最大的高度,一个是直接暴力遍历,这样求解该问题的时间复杂度是o(n^2);另一个是事先计算好,存放在数组里,用动态规划的办法,这样时间复杂度就只需要o(n)。

代码这里也贴一下,保证文章的完整度:

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

        vector<int> rightMax(n);
        rightMax[n - 1] = height[n - 1];
        for (int i = n - 2; i >= 0; --i) {
            rightMax[i] = max(rightMax[i + 1], height[i]);
        }

        int ans = 0;
        for (int i = 0; i < n; ++i) {
            ans += min(leftMax[i], rightMax[i]) - height[i];
        }
        return ans;
    }
};



作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/trapping-rain-water/solution/jie-yu-shui-by-leetcode-solution-tuvc/
来源:力扣(LeetCode)

一个优化的办法就是使用双指针,这样就不用o(n)的空间开销了。

要知道,求解左右最大高度的原因是为了确定当前元素的高度,即更小的那个元素值作为高度。

双指针在遍历的时候,并不是同时走,而是依据当前的左右最大高度值的信息进行判断。

明白一个点,如果说当前左元素值<当前右元素值,说明leftmax<rightmax。
这个点很重要,因为一开始左右最大高度是左右元素的当前值。(从头开始这样执行的话&&leftmax更新是依据当前元素和原来的leftmax比较而来)
当前元素能接的雨水为 leftmax-当前元素值,并且左指针++。

同理,当前左元素值>=当前右元素值,说明leftmax>=rightmax。当前元素能接的雨水为 rightmax-当前元素值,并且右指针–。

代码如下:

class Solution {
public:
    int trap(vector<int>& height) {
        int ans = 0;
        int left = 0, right = height.size() - 1;
        int leftMax = 0, rightMax = 0;
        while (left < right) {
            leftMax = max(leftMax, height[left]);
            rightMax = max(rightMax, height[right]);
            if (height[left] < height[right]) {
                ans += leftMax - height[left];
                ++left;
            } else {
                ans += rightMax - height[right];
                --right;
            }
        }
        return ans;
    }
};


作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/trapping-rain-water/solution/jie-yu-shui-by-leetcode-solution-tuvc/
来源:力扣(LeetCode)

2.单调栈

这个办法的思路就是说,将栈元素放入,如果当前元素小于栈顶元素则入栈(表示可以有机会储水),如果当前元素大于栈顶元素,开始出栈栈顶元素(表示开始计算储水),出栈后继续比较和新的栈顶元素的值,即继续计算储水量。这里可能有人会觉得多算了储水量。比如 3 2 0 2 3,在第二个2的时候计算了一遍储水,在第二个3的时候又来计算一遍,那不是多算了嘛?

其实没有,因为计算储水的高度公式为:当前元素和栈顶元素值中较小值–栈顶元素值。后面的减去栈顶元素值就是为了解决这个问题的。

代码如下:

class Solution {
public:
    int trap(vector<int>& height) {
        int ans = 0;
        stack<int> stk;
        int n = height.size();
        for (int i = 0; i < n; ++i) {
            while (!stk.empty() && height[i] > height[stk.top()]) {
                int top = stk.top();
                stk.pop();
                if (stk.empty()) {
                    break;
                }
                int left = stk.top();
                int currWidth = i - left - 1;
                int currHeight = min(height[left], height[i]) - height[top];
                ans += currWidth * currHeight;
            }
            stk.push(i);
        }
        return ans;
    }
};


作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/trapping-rain-water/solution/jie-yu-shui-by-leetcode-solution-tuvc/
来源:力扣(LeetCode)

总结

好像变懒了,不太愿意记录自己的刷题过程,因为觉得很浪费时间,要自己去总结,去对比,都没时间干题目了。但是转念一想,如果这个题目没有充分搞明白,不会举一反三,那么继续刷题下去的意义是什么,遇到类似的还是不会写,倒不如慢慢的,搞懂,搞透,急什么。毕竟有人它是下笔必得分,有人它是这里一点那里一点,无意义罢了。

其实更好的做法是把官方提供的思路,自己去实现一遍,可我没有,我只是知道了可以那样做,而我并没有复现一次。还是懒啊!!!(也许二刷的时候做?)

喜欢就点个赞叭!

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值