单调栈模块

1. 每日温度

在这里插入图片描述
遍历整个数组,如果栈不空,且当前数字大于栈顶元素,那么如果直接入栈的话就不是 递减栈 ,所以需要取出栈顶元素,由于当前数字大于栈顶元素的数字,而且一定是第一个大于栈顶元素的数,直接求出下标差就是二者的距离。(维护的是一个单调递减栈)

class Solution {
public:
//该方法只需要对数组进行一次遍历,每个元素最多被压入和弹出堆栈一次,算法复杂度是 O(n)
//维护一个单调递减栈
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        int n = temperatures.size();
        vector<int> v(n,0);
        stack<int> st;
        for(int i = 0;i<n;++i)
        {
            while(!st.empty() && temperatures[i] > temperatures[st.top()])
            {
                int index = st.top();
                st.pop();
                v[index] = i-index;
            }
            st.push(i);
        }
        return v;
    }
};

2. 下一个更大元素I

在这里插入图片描述
维护一个单调递减栈

class Solution {
public:
    vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
        int n = nums1.size();
        vector<int> result(n);
        unordered_map<int,int> map = helper(nums2);
        for(int i = 0;i<nums1.size();++i)
            result[i] = map[nums1[i]];
        return result;
    }
    //其实就是找数组2所对应每一个元素的下一个更大值
    //对于这个unordered_map,key值就是所对应的当前元素,value就是下一个比其大的数值
    unordered_map<int,int> helper(vector<int>& nums)
    {
        unordered_map<int,int> hashmap;
        stack<int> st;
        for(int i = 0;i<nums.size();++i)
        {
            //维护的是一个单调递减栈,来找下一个更大的元素
            while(!st.empty() && nums[i] > st.top())
            {
                hashmap[st.top()] = nums[i];
                st.pop();
            }
            st.push(nums[i]);
        }
        while(!st.empty())
        {
            hashmap[st.top()] = -1;
            st.pop();
        }
        return hashmap;
    }
};

2.1 下一个更大元素II

在这里插入图片描述
这道题其实最复杂的地方在于,怎么样去写这个循环数组,我们或许最开始的方法就是将原先的这个数组再给他拼接一遍,这个数组其实是可以使用取模的方法来进行模拟遍历的。就是维护一个单调递减栈,来进行模拟寻找下一个较大的温度

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

3. LeetCode第42题—接雨水

在这里插入图片描述
这道题需要维护的是一个单调递减栈,当不满足递减栈的时候,就发现了凹槽,此时栈顶元素就是凹槽的底部,i的位置就是凹槽的左边柱子,新的栈顶元素就是凹槽的左边柱子,那么此时这里就可以接雨水。
在这里插入图片描述

其实是通过 长 * 宽 来计算雨水面积的。

长就是通过柱子的高度来计算,宽是通过柱子之间的下标来计算
在这里插入图片描述

class Solution {
public:
    //对于接雨水来说,这里肯定是需要用到短板效应的
    int trap(vector<int>& height) {
        stack<int> st;
        int sum = 0;
        for(int i = 0;i<height.size();++i)
        {
            while(!st.empty() && height[i] > height[st.top()])
            {
                int cur = st.top();
                st.pop();
                if(!st.empty())
                {
                    int curheight = min(height[st.top()],height[i]) - height[cur];
                    int width = i - st.top() - 1;
                    sum += curheight*width;
                }
            }
            st.push(i);
        }
        return sum;
    }
};

4. LeetCode第84题—柱状图形中最大的矩形

在这里插入图片描述
题目解析:我想要找到能够完全包裹住每一个下标矩形的最大矩形,此道题使用的是一个单调递增的栈
在这里插入图片描述
使用简单点的方法,就是找到每个矩形向左边延申,找到比其小的,也就是不能包裹住当前下标矩形的,和向右边延申找到那个完全不能包裹住当前下标的矩形。

正式的解题思路

  1. heights数组左右各加一个0,这样栈永远非空且里面的非零元素都可以出栈从而计算相应高度的最大矩形面积(题目中说明了高度均大于0)
  2. 从左到右遍历heights数组
    栈空或当前元素>=栈顶,则表明以栈顶元素对应高的矩形面积还不能确定,所以就将新元素入栈,维持非严格单调递增栈
  3. 如果栈非空且当前元素<栈顶元素对应高,表明以栈顶元素值对应高的柱子找到最近的下一个更矮的柱子,由此可以确定以栈顶元素对应高的矩形面积了。矩形高就是栈顶元素值,右边界就是当前元素,左边界是栈顶元素的前一个元素。(当前元素就是这个矩形的高,那么第一个比这个小的左边界就是新的栈顶,第一个比当前下标小的右边界就是i这个位置)因为在上一步中我们知道栈中元素值对应高从栈底到栈顶非严格单调递增。因此矩形宽是当前元素下标与栈顶元素前一个元素的下标的差值-1。另外,栈顶元素出栈后,需要继续看当前元素是否<新栈顶元素值,如果是,就继续pop出栈顶元素,然后计算以该栈顶元素值为高的矩形面积,直到当前新元素值>新栈顶元素时,当前新元素值入栈
  1. heights = [2,1,5,6,2,3] 添加哨兵后heights = [0,2,1,5,6,2,3,0]

  2. heights[0]=0,入栈s=[0]

  3. heights[1]=2,入栈s=[0,1]

  4. heights[2]=1 < 2,pop出栈顶元素1,s=[0],可以确定heights[1]=2所能勾勒出的最大矩形面积,左边界为新栈顶元素0,右边界就是当前下标i=2,宽为2-0-1=1,高度为heights[1]=2,ans=max(0,2)=2。1>heights[0]=0,所以不继续pop,入栈s=[0,2]

  5. heights[3]=5,入栈s=[0,2,3]

  6. heights[4]=6,入栈s=[0,2,3,4]

  7. heights[5]=2 < heights[4]=6,pop出栈顶元素4,s=[0,2,3],可以确定heights[4]=6所能勾勒出的最大矩形面积,左边界为新栈顶元素3,右边界就是当前下标i=5,宽为5-3-1=1,高度为heights[4]=6,ans=max(2,6)=6

  8. heights[5]=2 < heights[3]=5,pop出栈顶元素3,s=[0,2],可以确定heights[3]=5所能勾勒出的最大矩形面积,左边界为新栈顶元素2,右边界就是当前下标i=5,宽为5-2-1=2,高度为heights[3]=5,ans=max(6,10)=10。2>heights[2]=1,所以不继续pop,入栈s=[0,2,5]

  9. heights[6]=3,入栈s=[0,2,5,6]

  10. heights[7]=0 < heights[6]=3,pop出栈顶元素6,s=[0,2,5],可以确定heights[6]=3所能勾勒出的最大矩形面积,左边界为新栈顶元素5,右边界就是当前下标i=7,宽为7-5-1=1,高度为heights[6]=3,ans=max(10,3)=10

  11. heights[7]=0 < heights[5]=2,pop出栈顶元素5,s=[0,2],可以确定heights[5]=2所能勾勒出的最大矩形面积,左边界为新栈顶元素2,右边界就是当前下标i=7,宽为7-2-1=4,高度为heights[5]=2,ans=max(10,8)=10
    heights[7]=0 < heights[2]=1,pop出栈顶元素2,s=[0],可以确定heights[2]=1所能勾勒出的最大矩形面积,左边界为新栈顶元素0,右边界就是当前下标i=7,宽为7-0-1=6,高度为heights[2]=1,ans=max(10,6)=10。不继续pop,入栈s=[0,7]

  12. 遍历结束,ans=10

  • 为啥要增加哨兵位呢?是因为如果不增加哨兵位,那么后面入栈的元素都没有办法弹出
  • 为什么使用单调栈的方式呢?既然我们目标是想左右两方向分别找出下一个更矮的柱子,那么就可以用单调递增栈,一次遍历即可确定左右边界,这样优化了时间复杂度
class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        heights.insert(heights.begin(),0);
        heights.push_back(0);
        stack<int> st;
        int res = 0;
        //栈里面存储的是下标,因为要计算宽度
        //这里维护的是一个单调递增的栈
        for(int i = 0;i<heights.size();++i)
        {
            while(!st.empty() && heights[i] < heights[st.top()])
            {
                int curIndex = st.top();
                st.pop();
                int curHeight = heights[curIndex];
                //此时新的栈顶就是左边第一个比其小的
                int leftIndex = st.top();
                //此时下标i就是右边第一个比其小的
                int rightIndex = i;
                int curwidth = rightIndex - leftIndex - 1;
                res = max(res,curwidth * curHeight);
            }
            st.push(i);
        }
        return res;
    }
};

5. LeetCode第224题—基本计算器

在这里插入图片描述
这道题还不是借助单调栈来解决问题的,其实更准确点说,是借助了栈的特性,来完成的题目

class Solution {
public:
    //因为这道题没有乘除,所以只需要正负号就完全可以
    int calculate(string s) {
        stack<int> ops;
        int sign = 1;
        ops.push(1);

        int ret = 0;
        int n = s.length();
        int i = 0;
        while (i < n) {
            if (s[i] == ' ') {
                i++;
            } else if (s[i] == '+') { 
                sign = ops.top();
                i++;
            } else if (s[i] == '-') {
                sign = -ops.top();
                i++;
            } else if (s[i] == '(') {
                ops.push(sign);
                i++;
            } else if (s[i] == ')') {
                ops.pop();
                i++;
            }
            else
            {
                long num = 0;
                while (i < n && s[i] >= '0' && s[i] <= '9') {
                    num = num * 10 + s[i] - '0';
                    i++;
                }
                ret += sign * num;
            }
        }
        return ret;
    }
};

6. LeetCode第239题—滑动窗口的最大值

链接: https://leetcode.cn/problems/sliding-window-maximum/
在这里插入图片描述
你会发现在从k开始下标哪里有个if语句,好像不是特别明白。那是因为当你开始继续向右滑动的时候,此时你所维护的双端队列里面的最大值是有可能已经不在是此时k个滑动窗口中的值,所以此时要考虑掉,将其去掉。还有一点就是,对于单调栈这种题目,我们保存的都是下标,而不是具体的数值。

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        if(k==0)
            return {};
		vector<int>res;
		deque<size_t>window;
		/*Init K integers in the list*/
		for (size_t i = 0; i < k; i++) {
			while (!window.empty()  && nums[i] > nums[window.back()]) {
				window.pop_back();
			}
			window.push_back(i);
		}
		res.push_back(nums[window.front()]);
		/*End of initialization*/
		for (size_t i = k; i < nums.size(); i++) {
            //你会发现这两句代码我咋死活不太懂是什么意思呢?
            //也就是如果发现了此时队列中的最大值,依旧跨越过了
			if (!window.empty() && window.front() <= i - k) {
				window.pop_front();
			}
			while (!window.empty() && nums[i] > nums[window.back()]) {
				window.pop_back();
			}
			window.push_back(i);
			res.push_back(nums[window.front()]);
		}
		return res;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值