数据结构第七节——单调栈和单调队列(1)
我们先前已经简单的学习了栈和队列两种优秀的的数据结构,通过他们的一些基础功能就已经可以帮我们便捷的解决很多棘手的问题。那么我们不禁会想,这两种强大的数据结构,还有没有什么特殊的性质可以更好的帮助我们解决一些问题呢?
答案是:有的。
众所周知,单调性无论在各个领域,都是一个及其优秀的性质,通过单调性,可以帮我们将很多麻烦的问题变得简便快捷。那么,当单调性和上述两种优秀的数据结构结合起来后,又将产生怎样的魔力呢?
下面我们就来欣赏,单调栈和单调队列的魅力~~~
思考:给你一个长度为n的数列,从前往后看,如何求出第一个比它大的元素的下标?
对于以上问题,如果采用暴力解法,那么我们可能需要对于每一个元素,依次遍历整个数组,将其
与数组中的数逐个比较。
那么,既然是比较大小,我们可否利用数列单调的性质,结合栈来解决问题呢?
当然可以!对于这个问题,我们可以利用一个单调递减的栈来解决这个问题,我们使栈顶元素最小。(保证栈的单调递减性)
- 首先我们将数列的第一个元素插入栈顶。
- 然后我们遍历整个数组
- 对于小于栈顶的元素,我们将其插入栈中。
- 对于大于栈顶的元素,为了保证栈的单调性不变,我们将栈内比它小的元素踢掉。此时,这个元素就是数列中第一个比 ” 被踢掉 “ 元素大的那个数 !那么,记录它的下标即可 !
- 最后,对于被剩下在栈中的元素,(即数列中没有比他们更大的数)规定比他们大的元素下表为0即可!
上例题~
一、求下一个更大数
现在给你nn个数字a1,a2,…,an,问每个数字往后看,第一个比它大的数字的下标是多少。如果没有则输出0。
输入格式
第一行一个整数n。
接下来一行共n个数。
输出格式
输出n个数,表示答案。
样例输入
7
2 6 3 1 5 7 4
样例输出
2 6 5 5 6 0 0
数据规模
对于100%的数据,保证1≤n≤2×105,1≤ai≤109。
代码实现
#include <bits/stdc++.h>
using namespace std;
const int N = 2 * 1e5 + 10;
int n,s[N],a[N],ans[N],top = 0;
int main()
{
//读入n个数
scanf("%d", &n);
for ( int i = 1; i <= n; i++ ) scanf("%d", &a[i]);
//遍历数组中每一个数
for ( int i = 1; i <= n; i++ ){ //Ps:栈中储存a中元素下标
//当栈顶元素存在 且 数组中元素 大于栈顶元素
while ( top && a[i] > a[ s[top] ] ){
ans[ s[top] ] = i; //对应下表的元素位置的答案找到 即为本次遍历的i值
--top; //删除找到答案的栈顶元素
}
s[++top] = i; //每次操作将一个a中元素下标存入栈中
}
//将栈中剩下的元素的定义为0
for ( int i = top; i ; i-- ){
ans [ s[i] ] = 0;
}
//遍历输出答案
for ( int i = 1; i <= n; i++ ) printf("%d ", ans[i] );
printf("\n");
return 0;
}
二、 最大矩形面积
在一张网格图中,网格共有n列,每列有一些格子被小蜗从底向上涂了色,现在给你每一列被涂色的格子的高度aiai,请你求出被涂色的格子组成的最大矩形的面积。
输入格式
第一行一个整数n,表示总列数。
接下来一行共n个数,表示每列染色的格子数。
输出格式
输出一个数,表示最大面积。
样例输入
5
1 2 5 3 4
样例输出
9
样例解释
第3、4、5列可以组成3×3的矩形,面积为9。
数据规模
对于100%的数据,保证1≤n≤2×105,1≤ai≤109。
分析
对于每列,分别找出其前后第一个小于它的元素,分别记下列数 l 、r 。
求出以该列为宽,区间长度为长的矩形面积。
比较所有这样的矩形,找出最大值。
此题用单调栈的思想解决。
每次将数列a中元素从头(或从尾)开始插入栈中,并删除高于目标行的元素,最后位于栈顶的元素,即为距离目标元素最近的小于它的元素,记下它的下标 l(或 r)。
#include <bits/stdc++.h>
using namespace std;
const int N = 2 * 1e5 + 10;
int n,s[N],top = 0, l[N],r[N], a[N];
long long ans[N];
int main()
{
scanf("%d", &n);
for ( int i = 1; i <= n; i++ ) scanf("%d", &a[i]);
//遍历n列
//利用单调栈求左值
for ( int i = 1; i <= n; i++ ){
while ( top && a[i] <= a[ s[top] ] ) --top;
if ( top ) l[i] = s[top];
else l[i] = 0;
s[++top] = i;
}
top = 0; //注意栈初始化
//利用单调栈求右值
for ( int i = n; i ; i-- ){
while ( top && a[i] <= a[ s[top] ] ) --top;
if ( top ) r[i] = s[top];
else r[i] = n + 1;
s[++top] = i;
}
//求出所有矩形面积
for ( int i = 1; i <= n; i++ ){
ans[i] =1LL * ( r[i] - l[i] - 1 ) * a[i];
}
//排序输出最大
sort ( ans, ans + n );
printf("%lld",ans[n-1] );
return 0;
}
然而事实证明,要优先判断栈中是否为空,才可继续进行
而至于下面这个和上面那个有什么区别,我debug了一下午,技术有限,看不明白,脑阔疼。
现在姑且遵循前人经验,背模板吧T_T
#include <bits/stdc++.h>
using namespace std;
const int N = 2 * 1e5 + 10 ;
int n,top = 0;
int a[N];
int l[N],r[N];
int s[N]; //栈
long long ans[N];
int main()
{
scanf("%d", &n);
for ( int i = 1; i <= n; i ++ ) scanf("%d" ,&a[i] );
for ( int i = 1; i <= n; i++ ){
s[++top] = i; //这边先存再判断不行
while ( a[ s[top] ] >= a[i] ) --top;
if ( s[top] ) l[i] = s[top];
else l[i] = 0;
}
top = 0;
for ( int i = n; i ; i-- ){
s[++top] = i;
while ( a[ s[top] ] >= a[i] ) --top;
if( s[top] ) r[i] = s[top];
else r[i] = n+1;
}
for ( int i = 1; i <= n; i++ ){
ans[i] = 1LL * ( r[i] - l[i] - 1 ) * a[i];
}
sort( ans, ans + n );
printf("%lld\n", ans[n-1]);
return 0;
}