LeetCodes刷题总结5——单调栈思想

前言

在关于一维数组的各类题目中,存在着一种神奇的思想,它不需要对一个数组来回恶心的遍历,也不需要苦思冥想动态规划方程,这就是单调栈。顾名思义,单调栈是一个栈,需要题目符合先进后出的方式,同时它是单调的,也就是栈里面的元素要么递增,要么递减,一般是边入栈边处理,遇到不单调的情况便进行处理。总之,在我们读取元素是从左到右的,而处理元素是从右到左的,就可以考虑单调栈的思想了。

模板

在这里提前说明,算法题是千变万化的,这个模板只能当作代码的初步骨架或解题思路,是我在一些算法题里总结的,不具有普适性。

//将数据全部假设为int
//s一般存储数组下标而不存储值,因为可以根据下标找值
int func(vector<int>& nums){ //一般参数只有一个题目数组
    int n=nums.size();
    int res; //结果
    stack<int> s; //定义辅助栈
    for(int i=0;i<n;i++){ //对原数组进行遍历(也可以用while循环)
        //当不满足单调栈,需要对栈顶进行处理
        while(!s.empty()&&nums[s.top()]?nums[i]){ //?一般是<或>
            ... //根据题目做某些操作
            s.pop();
        }
        s.push(i); //存储下标
    }
    return res;
}

例题1-每日温度

题目描述

739. 每日温度 - 力扣(LeetCode) (leetcode-cn.com)

给定一个整数数组 temperatures ,表示每天的温度,返回一个数组 answer ,其中 answer[i] 是指在第 i 天之后,才会有更高的温度。如果气温在这之后都不会升高,请在该位置用 0 来代替。

示例 1:

输入: temperatures = [73,74,75,71,69,72,76,73]
输出: [1,1,4,2,1,1,0,0]


示例 2:

输入: temperatures = [30,40,50,60]
输出: [1,1,1,0]


示例 3:

输入: temperatures = [30,60,90]
输出: [1,1,0]

  • 1 <= temperatures.length <= 105
  • 30 <= temperatures[i] <= 100

思路

题目意思简单明了,需要我们找到比当前温度更高的温度位置,典型的从左到右遍历,先处理最右边的数据,那就可以定义单调递减栈,遇到增加的情况不断处理栈顶。

代码如下:

class Solution {
public:
    vector<int> dailyTemperatures(vector<int>& temperatures) {
        int n=temperatures.size();
        stack<int> s;
        vector<int> res(n,0); //根据题目,没有升高的元素为0,那就全部初始化为0
        for(int i=0;i<n;i++){
            //正常栈里都是温度递减,当有增加情况开始处理
            while(!s.empty()&&temperatures[s.top()]<temperatures[i]){
                res[s.top()]=i-s.top();
                s.pop();
            }
            s.push(i);
        }
        return res;
    }
};

例题2-柱状图中最大的矩形

题目描述

84. 柱状图中最大的矩形 - 力扣(LeetCode) (leetcode-cn.com)

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。

求在该柱状图中,能够勾勒出来的矩形的最大面积。

示例 1:

输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10


示例 2:

输入: heights = [2,4]
输出: 4

  • 1 <= heights.length <=105
  • 0 <= heights[i] <= 104

思路

这道题先考虑暴力解法,对于每一根柱子进行左右扩散,求它所能达到的最大矩形面积,这种“中心点扩散”的思想外部遍历一遍,内部遍历两遍,时间复杂度很高。于是考虑单调栈思想,设一个递增单调栈,当遇到减少的元素说明栈顶那个柱子确定的矩形面积可以计算了,因此整体只需要遍历一遍就可以解决问题。

代码如下:

class Solution {
public:
    int largestRectangleArea(vector<int>& heights) {
        int n=heights.size();
        stack<int> s;
        int width,height;
        int res=0; //记录结果
        for(int i=0;i<n;i++){
            while(!s.empty()&&heights[s.top()]>heights[i]){
                height=heights[s.top()]; //当前柱子确定的矩形高度
                s.pop();
                //当前柱子确定的矩形宽度
                width=i; //s为空,其实就是i-0
                if(!s.empty()){
                    width=i-s.top()-1; //s不空,左侧是s.top()
                }
                res=max(res,height*width);
            }
            s.push(i);
        }
        while(!s.empty()){
            height=heights[s.top()];
            s.pop();
            width=n;
            if(!s.empty()){
                width=n-s.top()-1;
            }
            res=max(res,height*width);
        }   
        return res;
    }
};

这里宽度的计算需要仔细推敲,另外代码其实有冗余的地方,可以用哨兵的方式进行简化。

例题3-接雨水

题目描述

42. 接雨水 - 力扣(LeetCode) (leetcode-cn.com)

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例 1:

输入:height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出:6
解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 


示例 2:

输入:height = [4,2,0,3,2,5]
输出:9

  • n == height.length
  • 1 <= n <= 2 * 104
  • 0 <= height[i] <= 105

思路

这是一道非常经典的题目了,解法也有很多种。可以用动态规划从左到右遍历记录遇到过的最大值,再同样从右向左遍历一遍。也可以采用“中心点扩散”思路,不过时间复杂度较高。这里单调栈只需要一次遍历,我们可以维护一个单调递减栈,当遇到增加的柱子,说明栈顶元素可以形成低洼(前面一直在递减),把积水量记录下来,当遍历完毕,积水量也就计算完毕了。

代码如下:

class Solution {
public:
    int trap(vector<int>& height) {
        int res = 0;
        stack<int> s;
        int n = height.size();
        for (int i = 0; i < n; ++i) {
            while (!s.empty() && height[i] > height[s.top()]) {
                int top = s.top();
                s.pop();
                if (s.empty()) {
                    break;
                }
                int left = s.top();
                int currWidth = i - left - 1;
                //接水高度为两边最小减去当前柱子高度
                int currHeight = min(height[left], height[i]) - height[top];
                res += currWidth * currHeight;
            }
            s.push(i);
        }
        return res;
    }
};

写在最后

算法题目千千万万,虽说具体问题具体分析,但是题目总逃不过核心知识点。对一些题目做好总结偶尔再回顾一下思路比追求数量好的多。单调栈也是由一般规律演化来的一种思想,很多题目就是这样,先想暴力解法然后看能不能剪枝,或者用空间换时间,数据结构不就是干这事嘛,用特定的结构处理高时间复杂度的问题,所以空间换时间是个大体思想。另外就是,每个问题有其特定场景,尤其是某些细节,光在脑子里勾勒很容易乱,这时可以找个普遍的例子,在纸上写写画画,栈是怎样变化的,中间值是怎样计算的,下标是怎样找的,很快思路就通了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值