LeetCode单调栈经典题目(十)

目录

1. LeetCode739. 每日温度

2. LeetCode496. 下一个更大元素 I

3. LeetCode503. 下一个更大元素 II

4. LeetCode42. 接雨水

5. LeetCode84. 柱状图中最大的矩形


在一维数组中,要寻找任一元素的右边或者左边第一个自己大或小的元素的位置,就可以想到用单调栈了。——代码随想录

1. LeetCode739. 每日温度

思路:
1.创建辅助栈stk和结果数组answer
2.遍历温度数组,把第i天的温度temperatures[i]入栈,栈顶到栈底单调递减
3.如果当前温度大于栈顶温度,则不断出栈,并把相隔天数(i-stk.top())写入到结果集中

class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        //结果集,因为对于后续没有温度更高的,结果是0,所以默认都为0
        vector<int>answer(temperatures.size());
        //辅助栈
        stack<int>stk;
        //遍历数组
        for(int i=0;i<temperatures.size();i++){
            while(!stk.empty()&&temperatures[i]>temperatures[stk.top()]){
                answer[stk.top()]=i-stk.top();
                stk.pop();
            }
            stk.push(i);//存入下标,既能知道天数,又可以知道温度
        }
        return answer;
    }
};

时间复杂度:O(n),对于每个温度数组的元素下标,只有入栈和出栈各一次操作;
空间复杂度:O(n),需要维护一个辅助栈

2. LeetCode496. 下一个更大元素 I

思路:
1.创建辅助栈stk,结果集ans,映射表存储nums1中各元素和下标的对应
2.初始化结果集为-1
3.建立好nums1的映射关系
4.遍历nums2,找到nums1中的元素在nums2右边第一个比自己大的值,放入结果集中

卡点:因为nums1和nums2是元素方面的联系,所以可以通过哈希表来找到当前遍历元素在nums1中的对应下标

class Solution {
public:
    vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
        //结果集,默认-1
        vector<int>ans(nums1.size(),-1);
        //辅助栈
        stack<int>stk;//栈底到栈顶:单调递减
        //映射表
        unordered_map<int,int>recorded;
        //建立映射关系
        for(int i=0;i<nums1.size();i++){
            recorded[nums1[i]]=i;
        }
        //遍历nums2
        for(int i=0;i<nums2.size();i++){
            while(!stk.empty()&&nums2[i]>nums2[stk.top()]){
                if(recorded.count(nums2[stk.top()])>0){
                    ans[recorded[nums2[stk.top()]]]=nums2[i];
                }
                stk.pop();
            }
            stk.push(i);
        }
        return ans;
    }
};

3. LeetCode503. 下一个更大元素 II

class Solution {
public:
    vector<int> nextGreaterElements(vector<int>& nums) {
        vector<int>ans(nums.size(),-1);//结果集,默认-1
        stack<int>stk;//辅助栈,栈底到栈顶:单调递减
        
        //当第二次遍历到最大值时,就可以结束循环了
        //第一次遍历到最大值:后面的元素还没遍历到,需要继续处理
        //第二次遍历到最大值时,最坏情况下,后面的所有元素右边比自己更大的数刚好就是最大值
        //由于所有位置的元素都有可能是最大值,所以把所有元素都遍历两次,nums的所有元素肯定都处理好了
        for(int i=0;i<nums.size()*2-1;i++){
            while(!stk.empty()&&nums[i%nums.size()]>nums[stk.top()]){
                ans[stk.top()]=nums[i%nums.size()];
                stk.pop();
            }
            stk.push(i%nums.size());
        }
        return ans;
    }
};

4. LeetCode42. 接雨水

1.双指针:按列计算雨水面积
当前列的雨水面积=min(左边最大高度,右边最大高度)-当前列高度,因为宽度默认是1,取最小值是由于短板效应
因为我们只需要每一列柱子左边和右边的高度的最小值,所以可以用maxLeft和maxRight来记录,这样就避免了重复计算左右两边最大高度的操作
class Solution {
public:
    int trap(vector<int>& height) {
        int res=0;//结果
        vector<int>maxLeft(height.size());//左边最大高度
        vector<int>maxRight(height.size());//右边最大高度
        
        //动态规划
        maxLeft[0]=height[0];
        for(int i=1;i<height.size();i++){
            maxLeft[i]=max(maxLeft[i-1],height[i]);
        }
        maxRight[height.size()-1]=height[height.size()-1];
        for(int i=height.size()-2;i>=0;i--){
            maxRight[i]=max(maxRight[i+1],height[i]);
        }

        //计算雨水面积
        for(int i=1;i<height.size()-1;i++){
            res+=min(maxLeft[i],maxRight[i])-height[i];
        }
        return res;
    }
};

2.单调栈:按行计算雨水面积
辅助栈:栈底到栈顶(单调递减)
(1)当前高度大于栈顶对应高度:一旦出现更高的柱子,就出现凹槽了,需要计算雨水面积
(2)当前高度等于栈顶对应高度:栈顶元素弹出,当前元素入栈,因为我们需要相同高度的柱子的最右边那个来计算雨水面积
(3)当前高度小于栈顶对应高度:直接入栈

出现凹槽时,我们需要三个元素来计算面积:栈顶,栈顶的下一个元素,准备入栈的元素
栈顶:凹槽底下的柱子
栈顶的下一个元素:凹槽左边的柱子
准备入栈的元素:凹槽右边的柱子

class Solution {
public:
    int trap(vector<int>& height) {
        stack<int>stk;//辅助栈
        int res=0;//结果
        stk.push(0);//先让0入栈,因为已进入循环就要和stk.top()比较,所以先让0入栈
        for(int i=1;i<height.size();i++){
            if(height[i]<height[stk.top()])stk.push(i);
            else if(height[i]==height[stk.top()]){
                stk.pop();
                stk.push(i);
            }
            else{
                while(!stk.empty()&&height[i]>height[stk.top()]){
                    int mid=stk.top();
                    stk.pop();
                    if(!stk.empty()){
                        int h=min(height[stk.top()],height[i])-height[mid];
                        int w=i-stk.top()-1;
                        res+=h*w;
                    }
                }
                stk.push(i);
            }
        }
        return res;
    }
};

简化代码:
等于和小于的操作类似,而等于的更新栈顶元素不影响计算。如果栈顶和栈顶的下一个元素相同,则在大于的操作中h=0,不影响结果。等到栈顶下一个相等高度的柱子做凹槽底部时,h是正常的,w也不受影响,因为w只和stk.top()有关,和弹出的柱子无关
class Solution {
public:
    int trap(vector<int>& height) {
        stack<int>stk;//辅助栈
        int res=0;//结果
        stk.push(0);
        for(int i=1;i<height.size();i++){
            while(!stk.empty()&&height[i]>height[stk.top()]){
                int mid=stk.top();
                stk.pop();
                if(!stk.empty()){
                    int h=min(height[stk.top()],height[i])-height[mid];
                    int w=i-stk.top()-1;
                    res+=h*w;
                }
            }
            stk.push(i);
        }
        return res;
    }
};

卡点:没有考虑凹槽底部柱子高度,导致计算了很多多余的面积

5. LeetCode84. 柱状图中最大的矩形

1.双指针:
leftFirst[i]:0~i-1第一个比柱子i低的下标
rightFirst[i]:i+1~height.size()-1第一个比i低的下标
把leftFirst[0]初始化为-1,把rightFirst初始化为height.size(),能够最低的柱子计算出正确的矩形

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        int res=0;
        vector<int>leftFirst(heights.size());
        vector<int>rightFirst(heights.size());
        leftFirst[0]=-1;
        for(int i=1;i<heights.size();i++){
            int t=i-1;
            while(t>=0&&heights[i]<=heights[t])t=leftFirst[t];
            leftFirst[i]=t;   
        }
        rightFirst[heights.size()-1]=heights.size();
        for(int i=heights.size()-2;i>=0;i--){
            int t=i+1;
            while(t<heights.size()&&heights[i]<heights[t])t=rightFirst[t];
            rightFirst[i]=t;
        }
        
        for(int i=0;i<heights.size();i++){
            int sum=heights[i]*(rightFirst[i]-leftFirst[i]-1);
            res=max(res,sum);
        }
        return res;
    }
};

本题和接雨水的唯一区别就是本题是找左右两边第一个小于的柱子
2.单调栈:
栈底到栈顶:单调递增,因为需要找的是左右两边第一个小于自己的柱子
height[i]>height[stk.top()]:入栈
height[i]==height[stk.top()]:更新栈顶
height[i]<height[stk.top()]:计算最大矩形

在数组前后都插入0,避免原始数组单调递增而导致没有计算矩形就结束循环了
class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        stack<int>stk;
        int res=0;
        heights.insert(heights.begin(),0);
        heights.push_back(0);
        stk.push(0);

        for(int i=1;i<heights.size();i++){
            if(heights[i]>heights[stk.top()])stk.push(i);
            else if(heights[i]==heights[stk.top()])stk.top()=i;
            else{
                while(!stk.empty()&&heights[i]<heights[stk.top()]){
                    int h=heights[stk.top()];
                    stk.pop();
                    if(!stk.empty()){
                        int w=i-stk.top()-1;
                        res=max(res,h*w);
                    }
                }
                stk.push(i);
            }
        }
        return res;
    }
};

精简代码:
class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        stack<int>stk;
        int res=0;
        heights.insert(heights.begin(),0);
        heights.push_back(0);
        stk.push(0);

        for(int i=1;i<heights.size();i++){
            while(!stk.empty()&&heights[i]<heights[stk.top()]){
                int h=heights[stk.top()];
                stk.pop();
                if(!stk.empty()){
                    int w=i-stk.top()-1;
                    res=max(res,h*w);
                }
            }
            stk.push(i);
        }
        return res;
    }
};

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jomo.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值