单调栈
单调栈是栈内元素具有严格单调性的一种数据结构。
模板题链接:单调栈
由于我们要找到每个数左边第一个比它小的数,那么我们便可以发现:
对于栈中任意一个数,如果在它右边存在一个数比它小(或相等),那么这个数便是不可能被选中的,直接弹出栈即可。
于是我们便可以进行如下操作:
按序枚举每一个数,准备将当前这个数压栈时,从栈顶开始遍历,如果栈内当前元素比当前预进栈的数要大(或相等),则符合上述所说的性质,可以直接将其出栈,让栈顶位置减一。直到遇到一个数比当前预进栈的数小,则让栈顶位置加一,使其入栈。
需要注意的是,为了保证栈内必然存在一个数比当前预进栈的数要小,也就是为了防止数组越界,我们可以将栈中下标为0的位置设为-1。
代码实现:
#include <iostream> #include <algorithm> #include <cstdio> using namespace std; const int N = 1e5+10; int n; int a[N],top; int find(int x) { a[0]=-1; while(a[top]>=x)top--; if(top==0){a[++top]=x;return -1;} int now=a[top];a[++top]=x; return now; } int main() { scanf("%d",&n); for(int i=1;i<=n;i++) { int x; scanf("%d",&x); printf("%d ",find(x)); } return 0; }
单调栈算法,每个元素至多入栈一次、出栈一次,故时间复杂度为O(n)。借助单调栈处理问题的思想在于及时排除不可能的选项,保持策略集合的高度有效性和秩序性,从而为我们作出决策提供更多的条件和可能方法。
推荐经典习题:Largest Rectangle in a Histogram (POJ2559)
推荐实战练题:城市游戏
单调队列
单调队列与单调栈类似,是一种队列内元素具有严格单调性的数据结构。
模板题链接:滑动窗口
下面以求最大值为例:
对于队列内每一个数,如果在其右边存在一个比它大的数,则该数一定比其更优,故可将其出队。
我们从左到右枚举序列中的每一个数,然后执行以下三步操作:
1.如果当前队首元素下标已经超出了滑动窗口的范围,则队首元素出队。
2.若队尾元素小于预入队的数,则不断删除队尾元素,直到队尾元素的值大于该数,则将其插入队尾。
3.若当前枚举的数的下标已经大于等于滑动窗口的大小,则可以输出,当前队首元素即是最优解。
为了方便起见,我们在队列中存储数的下标。
代码实现:
#include <iostream> #include <algorithm> using namespace std; const int N = 1e6 + 10; int n,k; int a[N],q[N]; int main() { scanf("%d%d",&n,&k); for(int i=1;i<=n;i++)scanf("%d",&a[i]); int front=1,back=1; for(int i=1;i<=n;i++) { if(front<back&&q[front]<=i-k)front++; while(front<back&&a[q[back-1]]>=a[i])back--; q[back++]=i; if(i>=k)printf("%d ",a[q[front]]); } puts(""); front=1,back=1; for(int i=1;i<=n;i++) { if(front<back&&q[front]<=i-k)front++; while(front<back&&a[q[back-1]]<=a[i])back--; q[back++]=i; if(i>=k)printf("%d ",a[q[front]]); } puts(""); return 0; }
单调队列算法,每个元素至多入队一次、出队一次,故时间复杂度为O(n)。它的思想也是在决策集合(队列)中及时排除一定不是最优解的选择。
推荐经典习题:最大子序和 (CH1201)