维护一个栈,使得其存储的数据具有单调性,这样的栈叫做单调栈.
- 单调递增栈:数据从栈顶到栈底单调递增.
- 单调递减栈:数据从栈顶到栈底单调递减.
单调栈何时用:为任意一个元素找左边和右边第一个比自己大/小的位置用单调栈.
用递增单调栈还是递减单调栈:递减栈会剔除波谷留下波峰;递增栈剔除波峰留下波谷.
由于每个元素最多各自进出栈一次,复杂度是O(n).
对所有元素,找到其右边第一个大于该元素的位置,此题用单调递增栈.本题要求出下标,而单调栈可以很容易地找出元素,可以使用pair把元素和下标绑定起来处理.接下来只需要利用单调栈算法找到一个元素右侧第一个大于该元素的元素即可.
此题可以视为没有重复元素处理.
用矩形高度对应数字大小,以这个数据为例:
5 4 2 1 3 5
设置一个栈,从左到右依次处理这个数列:
4入栈:
现在栈里只有4,找不到右侧比4大的元素.
2和1入栈:
对于任意元素都没有在其右侧找到比他更大的元素.此时从栈顶到栈底是单调递增的,这是一个单调递增栈.
只要新的元素比栈顶小,就直接把新元素入栈.凡是处在栈内的元素都还没有找到它的解.
下一个元素是3,如果直接入栈会破坏栈的单调性:
假设3直接入栈了,会发现2和1都发现了其右侧第一个大于他们的元素,即3.
此时2和1找到了解,这就意味着不需要考虑3之后的元素会不会是他们的解了,并且2和1也不会成为之后元素的解,那么2和1就完全没有必要留在栈中了.
所以在3入栈之前把1,2弹出并记录其解为3,然后使3入栈.4对于单调性不受到也不产生影响.
这使得栈保持了从栈顶到栈底递增的状态.
现在尝试让5入栈,发现将会破坏单调性,于是从栈顶不断弹出元素直到使5入栈时单调性不改变.
依次弹出3,4并记录他们的解为5.然后5入栈.
已经没有剩余的元素需要处理了,现在要处理栈中剩下的元素.
由于任意时刻栈中元素一定是从栈顶到栈底单调递增的,所以此时栈中所有元素右侧元素都比自己小,从而得知他们都没有解.(现在让他们直接出栈即可,也可以放置不管)
至此,所有元素都处理完毕,即找到了解或者发现没有解.
这个过程中遵循了如下原则进行操作:
①对原数列从左到右依次处理.
②对于处理的每一个数,如果当前栈为空或者该数小于栈顶的数,使该数入栈.
③对于处理的每一个数,如果该数大于栈顶的数,不断地弹出栈顶直到该数小于栈顶的数或者栈为空.然后该数入栈.
④在③过程中,每弹出一个栈顶的数,就记录该数的解为正在处理的数.
上述原则可以在代码里体现,其中②③进行了融合.
#include <algorithm> #include <cstdio> #include <cstring> #include <iostream> #include <stack> using namespace std; typedef pair<int, int> P; stack<P> s; int n, ans[3000010], a[3000010]; int main(){ scanf("%d", &n); for(int i = 1; i <= n; i++){ scanf("%d", a + i); while(!s.empty() && s.top().first < a[i]){ ans[s.top().second] = i; s.pop(); } s.push(make_pair(a[i], i)); } for(int i = 1; i <= n; i++) printf("%d ", ans[i]); return 0; }