LeetCode第 42 题:接雨水(C++)

42. 接雨水 - 力扣(LeetCode)
在这里插入图片描述

这个题算是典型题了,很多笔试题或者面试的时候都会考这个的变体。

先写个暴力看一看,思路在注释里:

class Solution {
public:
    int trap(vector<int>& height) {
        int res = 0;
        for(int i = 1; i < height.size(); ++i){//对于每一列
            int left_m = height[i], right_m = left_m;
            //左边的最大值
            for(int j = 0; j < i; ++j) left_m = max(left_m, height[j]);
            //右边的最大值
            for(int j = i+1; j < height.size(); ++j) right_m = max(right_m, height[j]);
            int m = min(left_m, right_m);//取两者之间较小的
            res  += m > height[i] ? m-height[i] : 0;
        }
        return res;
    }
};

能够通过314个测试用例,最后一个超时,至少说明思路是对的。

下面就需要考虑怎么优化,因为暴力法重复计算很多,每一次都需要计算出两边的最大最小值,那先来简单的:如果计算出全局最大值,那么全局最大值的左边元素的右边的最大值是不是就不用计算了呢?同理右边元素的左边最大值不用计算了,因为必然是全局最大值。虽然只是小优化,但是可以让我们ac,笔试的时候或许能帮上大忙:

class Solution {
public:
    int trap(vector<int>& height) {
        int res = 0;
        int idx = max_element(height.begin(), height.end()) - height.begin();//最大元素的下标
        for(int i = 1; i < height.size(); ++i){
            if(i == idx)    continue;
            int left_m = height[i], right_m = left_m;//左右两边的最大值
            if(i < idx){
                //左边的最大值
                for(int j = 0; j < i; ++j) left_m = max(left_m, height[j]);
                right_m = height[idx];//右边最大值肯定是全局最大值
            } else{
                left_m = height[idx];//左边最大值肯定是全局最大值
                //右边的最大值
                for(int j = i+1; j < height.size(); ++j) right_m = max(right_m, height[j]);
            }
            int m = min(left_m, right_m);//取两者之间较小的
            res  += m > height[i] ? m-height[i] : 0;
        }
        return res;
    }
};

现在我们至少是通过了(260ms)。
再接着优化吧,试想如果我们计算出数组三等分点的三个最大值,是不是又能减少计算量了,至少对于三等分的中间部分,左右的最大值都可以直接取了。那如果是四等分,五等分呢?想想都有点激动,不过计算最大值本身就是比较耗时的。再想想,我们在从左往右遍历的时候,其实我们已经访问过每个元素了,但是访问过后就丢弃了,当我们考虑后面的元素的时候,又需要回来再遍历寻找最大值。那我们可不可以遍历的时候,就记录下每个位置左边的最大值是多少呢?当然是可以的,其实这就是单调栈的思路。

先写下来看一看:

这儿需要注意右边的单调栈需要从右往左遍历进行维护,所以需要先逆序遍历一次把右边的单调栈计算出来,而左边的单调栈可以一边计算雨水值得时候一边维护(当然也可以事先计算出来,区别并不大):

class Solution {
public:
    int trap(vector<int>& height) {
        int res = 0;
        vector<int> m_l(height);//维护左边最大值的单调栈,m[i]是下标i的元素的左边元素的最大值
        vector<int> m_r(height);//维护右边最大值的单调栈,m[i]是下标i的元素的右边元素的最大值
        for(int i = height.size()-2; i >= 0; --i){//维护右边单调栈(逆序遍历)
            if(height[i] < m_r[i+1]) m_r[i] = m_r[i+1];
            else m_r[i] = height[i];
        }
        for(int i = 1; i < height.size(); ++i){
            //维护左边单调栈
            if(height[i] < m_l[i-1])  m_l[i] = m_l[i-1];
            else m_l[i] = height[i];

            int left_m = m_l[i];//左边的最大值
            int right_m = m_r[i];//右边的最大值
            int m = min(left_m, right_m);//取两者之间较小的
            res  += m > height[i] ? m-height[i] : 0;
        }
        return res;
    }
};

优化以下代码逻辑:

class Solution {
public:
    int trap(vector<int>& height) {
        int res = 0;
        //维护左(右)边最大值的单调栈,m[i]是下标i的元素的左(右)边元素的最大值
        vector<int> m_l(height), m_r(height);
        for(int i = height.size()-2; i >= 0; --i)//维护右边单调栈(逆序遍历)
            m_r[i] = height[i] < m_r[i+1] ? m_r[i+1] : height[i];
        
        for(int i = 1; i < height.size(); ++i){
            m_l[i] = height[i] < m_l[i-1] ? m_l[i-1] : height[i];//维护左边单调栈
            int m = min(m_l[i], m_r[i]);//取两者之间较小的
            res  += m > height[i] ? m-height[i] : 0;
        }
        return res;
    }
};

ok,现在得效率就可以达到4ms了。

题解里还看到一个递减栈的思路,一次遍历就可以,有点像括号匹配的思路:

【接雨水】单调递减栈,简洁代码,动图模拟 - 接雨水 - 力扣(LeetCode)
在这里插入图片描述

class Solution {
public:
    int trap(vector<int>& height) {
        int res = 0, cur = 0;
        stack<int> s;//相当于是递减栈
        while(cur < height.size()){
            while(!s.empty() && height[cur] > height[s.top()]){
                int top = s.top(); s.pop();
                if(s.empty())   break;//只有一个,无法积水
                int dis = cur - s.top()-1;//宽
                int bound_height = min(height[cur], height[s.top()]) - height[top];//高
                res += dis*bound_height;
            }
            s.push(cur++);
        }
        return res;
    }
};
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值