4.单调栈

基础知识

单调栈的本质是空间换时间,因为在遍历的过程中需要用一个栈来记录右边第一个比当前元素高的元素,优点是整个数组只需要遍历一次。直白来说,就是用一个栈来记录遍历过的元素。

单调栈里存放的元素是?:单调栈中存放的元素是 元素的下标i
单调栈中元素是递增的还是递减的?:以使用递增循序为例子(再强调一下是指从栈头到栈底的顺序),因为只有递增的时候,栈里要加入一个元素i的时候,才知道栈顶元素在数组中右面第一个比栈顶元素大的元素是i。即如果求一个元素右边第一个更大元素,单调栈就是递增的,如果求一个元素右边第一个更小元素,单调栈就是递减的。

使用单调栈需要拿出栈顶元素T[st.top()与所遍历元素进行比较,主要有三个判断条件。
当前遍历的元素T[i]小于栈顶元素T[st.top()]的情况
当前遍历的元素T[i]等于栈顶元素T[st.top()]的情况
当前遍历的元素T[i]大于栈顶元素T[st.top()]的情况

题目

1.每日温度

题目链接
给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指对于第 i 天,下一个更高温度出现在几天后。如果气温在这之后都不会升高,请在该位置用 0 来代替。
暴力解法:两层for循环,就可以找到i天时之后出现更高温度的天数,时间复杂度是O(n^2)
单调栈:通常是一维数组,要寻找任一个元素的右边或者左边第一个比自己大或者小的元素的位置,此时我们就要想到可以用单调栈了。时间复杂度为O(n)

    vector<int> dailyTemperatures(vector<int>& temperatures) {
        std::stack<int> st;
        std::vector<int> res(temperatures.size(), 0);
        st.push(0);
        for(int i=1; i<temperatures.size(); i++){
            if(temperatures[i]<=temperatures[st.top()]) st.push(i);
            else{
                while(!st.empty() && temperatures[i]>temperatures[st.top()]){
                    res[st.top()] = i-st.top();
                    st.pop();
                }
                st.push(i);
            }
        }
        return res;
    }

时间复杂度:O(n);空间复杂度:O(n)


2.下一个更大的元素1

题目链接
在这里插入图片描述
题目示例中我们可以看出最后是要求nums1的每个元素在nums2中下一个比当前元素大的元素,那么就要定义一个和nums1一样大小的数组result来存放结果。nums1是nums2的子集(也就是或者包括前者),而且题目说给定的数组是没有重复元素,因此可以使用哈希表unordered_set建立num1的映射——这一步是将num2所存入的stack.top()所对应数值映射到nums1数值所对应的下标,根据数值快速找到下标。

    vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
        std::stack<int> st;
        std::vector<int> res(nums1.size(), -1);
        if(nums1.size()==0) return res;

        std::unordered_map<int, int> umap;
        for(int i=0; i<nums1.size(); i++) umap[nums1[i]]=i;

        st.push(0);
        for(int i=1; i<nums2.size(); i++){
            if(nums2[i]<=nums2[st.top()]) st.push(i);
            else{
                while(!st.empty() && nums2[i]>nums2[st.top()]){
                    if(umap.count(nums2[st.top()])>0){
                        int index = umap[nums2[st.top()]];
                        res[index]=nums2[i];
                    }
                    st.pop();
                }
                st.push(i);
            }
        }
        return res;
    }

时间复杂度:O(n);空间复杂度:O(n)


3.下一个更大的元素2

题目链接
在这里插入图片描述
简单的做法是:直接将两个nums数组拼接在一起,使用单调栈计算出每一个元素的下一个最大值,最后再把结果集即result数组resize到原数组大小就可以了,直接对nums进行扩容操作。

    vector<int> nextGreaterElements(vector<int>& nums) {
        // 拼接一个新的nums
        vector<int> nums1(nums.begin(), nums.end());
        nums.insert(nums.end(), nums1.begin(), nums1.end());
        // 用新的nums大小来初始化result
        vector<int> result(nums.size(), -1);

但其实不扩容,而是在遍历时模拟走两边nums——这要在for循环的设置时进行巧妙的设计。

    vector<int> nextGreaterElements(vector<int>& nums) {
        int len = nums.size();
        std::vector<int> res(nums.size(), -1);
        if(nums.size()==0) return res;
        std::stack<int> st;
        st.push(0);
        for(int i=1; i<len*2; i++){
            if(nums[i % len] <= nums[st.top()]) {
                st.push(i%len);
            }
            else{
                while(!st.empty() && nums[i%len]>nums[st.top()]){
                    res[st.top()]=nums[i%len];
                    st.pop();
                }
                st.push(i%len);
            }
        }
        return res;
    }

时间复杂度:O(n);空间复杂度:O(n)


4.接雨水-困难

题目链接
在这里插入图片描述
接雨水问题在面试中常见的题目,代码随想录中给出三种解法
暴力解法:(第一个柱子和最后一个柱子不接雨水)按列方向计算雨水的,如何确定每一列能存储多少雨水?这是由当前列格子M的左最高格子L,右最高格子R之间的较小值与当前格子M差值。每次遍历列的时候,还要向两边寻找最高的列,所以时间复杂度为O(n^2),空间复杂度为O(1)。
双指针优化法:根据暴力解法所得当前列雨水的计算公式:min(左边柱子的最高高度,记录右边柱子的最高高度) - 当前柱子高度;对于对应i格子的左,右最高格子高度是可以计算的,所以可以预先通过双指针法计算出来,分别用一个数组存储,这就避免了重复计算。
单调栈:单调栈方法是按行方向计算雨水的;
1.单调栈内元素的顺序:从栈头(元素从栈头弹出)到栈底的顺序应该是从小到大的顺序,解释发现添加的柱子高度大于栈头元素了,此时就出现凹槽了,栈头元素就是凹槽底部的柱子,栈头第二个元素就是凹槽左边的柱子,而添加的元素就是凹槽右边的柱子
在这里插入图片描述
2.遇到相同高度的柱子nums[st.top()]==nums[i]时,遇到相同的元素,更新栈内下标,就是将栈里元素(旧下标)弹出,将新元素(新下标)加入栈中,因为要保证栈是凹槽的形状才能计算面积(左右两端)。
3.st要保存的是什么值,计算雨水面积是通过长*宽,长是柱子高度来计算=min(Left, Right)-Mid-nums[stack.top()],宽度是计算(Righit-Left)下标得到的。

    int trap(vector<int>& height) {
        if(height.size()<=2) return 0;
        std::stack<int> st;
        st.push(0);
        int res=0;
        for(int i=1; i<height.size(); i++){
            if(height[i]<height[st.top()]) st.push(i);
            else if(height[i]==height[st.top()]){
                st.pop();
                st.push(i);
            }
            else{
                while(!st.empty() && height[i]>height[st.top()]){
                    int mid = st.top();
                    st.pop();
                    if(!st.empty()){
                        int h = min(height[st.top()], height[i])-height[mid];
                        int w = i-st.top()-1;
                        res += h*w;
                    }
                }
                st.push(i);
            }
        }
        return res;
    }

5.柱状图中最大的矩形-困难

题目链接
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。这与4.接雨水题目不同是,前者是求柱状图外侧凹槽所能形成的最大面积,后者是就柱状图里面能形成的最大面积矩形。
在这里插入图片描述
暴力解法:代码随想录中给出以此遍历每个柱状体i,然后遍历左,右两侧,找到比当前i矮柱状体的下标,此时当前柱状体的高度就是h,左,右两侧柱状体之差就是矩形的宽d,即可求出面积。
单调栈:单调栈是单调递减的(从栈头到栈尾),逻辑是找到每个柱子左右两边第一个小于该柱子的柱子,因此这题与4.接雨水的思路是相反的。然后栈顶和栈顶的下一个元素以及要入栈的三个元素组成了我们要求最大面积的高度和宽度。

	// 暴力解法
    int largestRectangleArea(vector<int>& heights) {
        std::vector<int> minL(heights.size());
        std::vector<int> minR(heights.size());
        minL[0]=-1; //作为minL的退出while标志
        minR[heights.size()-1]=heights.size(); //作为minR的退出while标志
        for(int i=1; i<heights.size(); i++){
            int t = i-1;
            while(t>=0 && heights[t]>=heights[i]) t=minL[t];
            minL[i]=t;
        }
        for(int i=heights.size()-1; i>=0; i--){
            int t = i+1;
            while(t<heights.size() && heights[t]>=heights[i]) t = minR[t];
            minR[i]=t;
        }

        int res=0;
        for(int i=0; i<heights.size(); i++){
            int sum = heights[i]*(minR[i]-minL[i]-1);
            res = max(sum, res);
        }
        return res;
    }
    
	// 单调栈法
    int largestRectangleArea(vector<int>& heights) {
        int res = 0;
        std::stack<int> st;
        heights.insert(heights.begin(), 0);
        heights.insert(heights.end(), 0);
        st.push(0);

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

注意:使用单调栈法时 在height数组的顶部,尾部,插入了一个0

  1. 尾部插入0是为了处理递增数组时(对应的单调栈是单调递减的),右侧一直没有出现比当前小的值,就不会触发计算面积的公式,因此加入0值,就能让栈里的所有元素;
  2. 头部加入0,是为了处理递减数组时(对应的单调栈就一直压入,弹出,没有元素),因此加入0,是为了避免空栈取值(计算时stack.top()的左侧没有元素),直接跳过了计算结果的逻辑。
  • 24
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值