文章目录
方便自己预习也帮大佬们复习
概述
概念:
顾名思义,单调递增或递减的栈和队列
灵活性极强,也是一种思想
目前题型不多,会慢慢更新
下列代码均为AC代码,请放心食用
单调栈和单调队列并无固定的用法,也没有固定的区分,具体用哪个取决于你的解法该用到哪个数据结构,切记:只是一种思想
经典入门题
单调数列(尝试用单调栈或队列来写)
题目描述:
给定一个数列,请判断它是否单调("<=“和”>=“均为单调标识符),是则返回"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.柱状图中最大矩阵
题目描述:
给定 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.求区间最小值乘区间和的最大值与此区间
题目描述:
给定一个数组,求一区间,使得区间内最小值乘该区间内所有元素的和最大
输入描述:
第一行一个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;
}