单调栈MonotonicStack/单调队列MonotonicQueue入门与实战(C++)


方便自己预习也帮大佬们复习

概述

概念:
顾名思义,单调递增或递减的栈和队列
灵活性极强,也是一种思想


目前题型不多,会慢慢更新
下列代码均为AC代码,请放心食用

单调栈和单调队列并无固定的用法,也没有固定的区分,具体用哪个取决于你的解法该用到哪个数据结构,切记:只是一种思想

经典入门题

单调数列(尝试用单调栈或队列来写)

leetcode测试链接

题目描述:
给定一个数列,请判断它是否单调("<=“和”>=“均为单调标识符),是则返回"true”,否则返回"false"

样例:
输入:[1,2,2,3]
输出:true

输入:[6,5,4,4]
输出:true

输入:[1,3,2]
输出:false

输入:[1,2,4,5]
输出:true

输入:[1,1,1]
输出:true

1 <= A.length <= 50000
-100000 <= A[i] <= 100000
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
1.存一个单调递增栈与一个单调递减栈
2.递增栈:碰到小于前面的数时就把前面的数弹出递增栈
  递减栈:碰到大于前面的数时就把前面的数弹出递减栈
  此数入两栈
3.判断有没有栈跟数组一样长,一样长就说明有未曾弹出过的栈,此为单调数组
//用到的库函数:
//.top()返回栈顶元素
//.pop()弹出栈顶元素
//.empty()判断栈是否为空
class Solution {
    
    
public:
    bool isMonotonic(vector<int>& a) {
    
    
        int len=a.size();
        stack<int>up_num;
        stack<int>down_num;
        for(int i=0;i<len;i++)
        {
    
    
            while(!up_num.empty()&&a[i]<up_num.top()) up_num.pop();//while先判前再判后,别写反了
            while(!down_num.empty()&&a[i]>down_num.top()) down_num.pop();
            up_num.push(a[i]);
            down_num.push(a[i]);
        }
        if(up_num.size()==len||down_num.size()==len) return true;//若有一个按序存完了,就是单调的
        return false;
    }
};

简单单调栈、队列

1.我的发型太太太太太丑啦!!!

牛客测试链接

题目描述:
有n头奶牛,它们都有自己的身高且能意识到自己的发型很丑,每头奶牛都只能看见比自己身高低的奶牛的头发(它们不能绕过比自己高的去看别人头发),且所有奶牛在排队时均面向队尾(即第一头奶牛面向最后一头奶牛),但凡>=观察身高的都能阻拦其视线,你帮主人一个忙,看看所有发型被看到的次数总和是多少。

输入描述:
第一行输入一个N代表奶牛数
第二到N+1行每行输入一个整数h代表每头奶牛的身高(此时第一个数是队首)
(1 ≤ N ≤ 80,000)(1 ≤ h ≤ 1,000,000,000)

输出描述:
一个整数

样例:
输入:
6
10 3 7 4 12 2
输出:
5
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
1.与入门题类似,只需要保证此栈单调即可
2.如果当前身高比前一个要高,那大可不必留着前面那个,因为反正新压入的它也看不见,pop掉即可
3.在每次压入栈顶前(说明它底下的牛都能看见它的头发),都将栈内元素个数加到统计变量中
//本题使用到的库函数与入门题一样
#include <stack>
#include <iostream>
using namespace std;
typedef long long ll;
int main()
{
    
    
    stack<int>down_cow;//递减的奶牛高度栈
    int n;
    int h;
    ll sum=0;//统计变量
    cin>>n;
    for(int i=0;i<n;i++)//与入门题差不多,理解参考入门题
    {
    
    
        cin>>h;
        while(!down_cow.empty() && h>=down_cow.top()) down_cow.pop();
        sum+=down_cow.size();
        down_cow.push(h);
    }
    cout<<sum;
    return 0;
}

思维单调题

1.柱状图中最大矩阵

leetcode测试链接
POJ测试链接

题目描述:
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
在这里插入图片描述
以上是柱状图的示例,其中每个柱子的宽度为 1,给定的高度为 [2,1,5,6,2,3]。
在这里插入图片描述
图中阴影部分为所能勾勒出的最大矩形面积,其面积为 10 个单位。

样例:
输入: [2,1,5,6,2,3]
输出: 10
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

方法一:最简易懂单调栈
思路:
1.分析问题时考虑到矩形面积为底乘高,
  所以我们要优化底的求法(right-left+1)
2.在求right与left时考虑此h[i]如何从i向两边扩散
  就有了下面这个代码(若你想到的方法是下面这种方法,请务必优化)
class Solution {
    
    
public:
    int largestRectangleArea(vector<int>& h) {
    
    
        if(h.size()==0) return 0;
        else if(h.size()==1) return h[0];
        int len=h.size();
        vector<int>left(len);//可扩散的最左下标
        vector<int>right(len);//可扩散的最右下标
        stack<int>histogram_id;//记录下标
//这里的while是固定h,然后搜索左边界与右边界
//单调栈模板,递增栈:记录下标,若碰到此下标代表的数小的,不断向前pop,直到成立为止,最后一个下标即为此i的扩散边界
        for(int i=0;i<len;i++)//从右往左取单调递增栈
        {
    
    
            while(!histogram_id.empty()&&h[i]<=h[histogram_id.top()]) histogram_id.pop();
            left[i]=(histogram_id.empty()? -1:histogram_id.top());
            histogram_id.push(i);
        }
        histogram_id=stack<int>();//重置栈
        for(int i=len-1;i>=0;i--)//从右往左取单调递增栈
        {
    
    
            while(!histogram_id.empty()&&h[i]<=h[histogram_id.top()]) histogram_id.pop();
            right[i]=(histogram_id.empty()? len:histogram_id.top());
            histogram_id.push(i);
        }
        int max1=0;
        for(int i=0;i<len;i++) max1=max(max1,h[i]*(right[i]-left[i]-1));//后者为此位置上的h[i]所求得的面积
        return max1;
    }
};

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

方法二:进阶一小步单调栈
思路:
与上面的总体思路一致,但要优化left和right的取值
不难发现,在pop结束的时候,栈顶元素+1就是左边界,当前i-1就是右边界
class Solution {
    
    
public:
    int largestRectangleArea(vector<int>& h) {
    
    
        if(h.size()==0) return 0;
        else if(h.size()==1) return h[0];
        h.insert(h.begin(),0);
        h.push_back(0);//设置前后边界,防止在while的时候造成因无法比较而形成的运行错误
        int len=h.size();
        stack<int>histogram_id;//记录下标
        int max1=0;//维护最大值
        for(int i=0;i<len;i++)
        {
    
    
            //这是已知右边界之后移动h,每一次都搜出它的左边界
            while(!histogram_id.empty()&&h[i]<h[histogram_id.top()])//我们要保证当前栈顶+1为左边界,所以存在于栈内的元素必须严格单调递增(前"<"后)
            {
    
    
                int id=histogram_id.top();
                histogram_id.pop();
                int left=histogram_id.top()+1;//因为左边严格小于,所以左边界就是他的左边下标
                int right=i-1;//右边界是当前出现不协调位置时开始pop的起始下标
                max1=max(max1,(right-left+1)*h[id]);
            }
            histogram_id.push(i);
        }
        return max1;
    }
};
方法三:单调栈原理转换

.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:空间换时间,拿数组储存,模拟单调栈
这是第一种方法的数组型,第二种自己尝试更改即可
//由于C++代码输入给的是vector还是很慢,但Java给的是数组型输入,有意向的小伙伴可以试试哦
class Solution {
    
    
    public int largestRectangleArea(int[] h) {
    
    
        int len=h.length;
        if(len==0) return 0;
        if(len==1) return h[0];
        int[] left=new int[len];
        int[] right=new int[len];
        int[] stack=new int[len+1];
        int top=0;
        for(int i=0;i<len;++i)
        {
    
    
            while(top>0&&h[stack[top]]>=h[i]) top--;
            left[i]=(top==0)? -1:stack[top];
            stack[++top]=i;
        }
        top=0;
        for(int i=len-1;i>=0;--i)
        {
    
    
            while(top>0&&h[stack[top]]>=h[i]) top--;
            right[i]=(top==0)? len:stack[top];
            stack[++top]=i;
        }
        int max1=0;
        for(int i=0;i<len;++i) max1=Math.max(max1,h[i]*(right[i]-left[i]-1));
        return max1;
    }
}
2.求区间最小值乘区间和的最大值与此区间

POJ测试链接

题目描述:
给定一个数组,求一区间,使得区间内最小值乘该区间内所有元素的和最大

输入描述:
第一行一个n
第二行n个整数a[i](i为从1到n)
1 <= n <= 100000
0<=a[i]<=1000000

输出描述:
第一行一个整数代表这个最大值
第二行为两个被空格分隔的整数代表这个区间的左右下标

样例:
输入:
6
3 1 6 4 5 2
输出:
60
3 5
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.

思路:
1.用前缀和求区间和
2.与上题方法二的思路一样,具体看注释
#include <stack>
#include <iostream>
#include <vector>
#include <algorithm>
#include <string>
/*#define cin(a) scanf("%d",&a)
#define llcin(a) scanf("%lld",&a)
#define fcin(a) scanf("%lf",&a)
#define cout(a) printf("%d",a)
#define cout_n(a) printf("%d\n",a)
#define cout_d(a) printf("%d ",a)
#define f2cout(a) printf("%.2f",a)
#define f2cout_n(a) printf("%.2f\n",a)
#define f2cout_d(a) printf("%.2f ",a)
#define llcout(a) printf("%lld",a)
#define llcout_n(a) printf("%lld\n",a)
#define llcout_d(a) printf("%lld ",a)*/
using namespace std;
typedef long long ll;
const int maxn = 1e5 + 10;
int a[maxn];
ll sum[maxn];  //前缀和(求区间)
stack<int> id; //记录下标
int main()
{
    
    
    int n;
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
    {
    
    
        scanf("%d", &a[i]);
        sum[i] = sum[i - 1] + a[i];
    }
    a[n + 1] = -1; //设置边界
    ll max1 = -1;
    int pos1, pos2, cur;
    for (int i = 1; i <= n + 1; i++)
    {
    
    
        if (id.empty() || a[id.top()] <= a[i])//不考虑,插入i后直接跳过
        {
    
    
            id.push(i);
            continue;
        }
        while (!id.empty() && a[id.top()] > a[i])//单调递减栈
        {
    
    
            cur = id.top();
            id.pop();
            if ((sum[i - 1] - sum[cur - 1]) * a[cur] > max1)//维护最大值与此时左右边界
            {
    
    
                max1 = (sum[i - 1] - sum[cur - 1]) * a[cur];
                pos1 = cur;
                pos2 = i;
            }
        }
        id.push(cur);
        a[cur] = a[i];//将pop完的重新压入
    }
    printf("%lld\n%d %d", max1, pos1, pos2 - 1);
    return 0;
}

猜你喜欢

转载自blog.csdn.net/SnopzYz/article/details/113109153