顾名思义,单调栈就是栈内元素从栈顶到栈底单调递增或者单调递减的栈,这一点和单调队列很相似,但是单调栈只能在栈顶操作。
我们借用拿号排队的场景来说明。
现在有很多人在排队买可乐,每个人手里都拿着号,越靠前的人手里的号越小,但是号不一定是连续的。有一个人拿了号后并没有去排队,而是跑去约会了。等他回来后,发现队伍已经排得很长了,他不能直接插入到队伍里,不然人家以为他是来插队的。于是他跑到队伍最后,挨个询问排队人手里的号码是多少,他认为号比他大的人都是“插队”的,于是施魔法把这些人变消失,直到找到号比他小的为止。
在上面这个场景里,同学们排的队伍就像是单调栈,因为同学们手里拿的号是单调递增的。一个同学找到自己位置并加入队伍中的这个过程就是元素加入单调栈的过程。如果新加入的元素加到栈顶后,栈里的元素就不再是单调递增了,那么我们就删除加入前的栈顶元素,就像施魔法把“插队”的人变消失一样。只有当新元素加入后,栈依然是单调递增的,我们才把元素加进栈里。
题目:给定一个包含若干整数的数组,对于其中每个元素 a r r i arr_i arri,计算左边离它最近的比 a r r i arr_i arri 更小的元素。
解法:给定一个包含若干个整数的数组,我们从第 1 个元素开始依次加入单调栈里,并且加入后更新单调栈。那么单调栈有这样的性质:对于从栈顶到栈底单调递减的栈,如果此时栈顶元素为b,加入新元素a后进行更新时,如果a大于b,说明如果从a在数组中的位置开始往左边遍历,则b一定是第一个比a小的元素;如果a小于b,那么对于a右侧的元素来说,b就失去了比较的意义,因此将b从栈中弹出,并继续让a和栈顶元素判断。
解法的伪代码如下:
get_left_smaller(arr, n)
s = new Stack
for element in arr
while s is not empty and element < s.top
s.pop
if element > s.top
s.push(element)
单调栈的维护是 m a t h c a l O ( n ) mathcal{O}(n) mathcalO(n) 级的时间复杂度,因为所有元素只会进出栈各一次。
我们来看看这样一道题:地上从左到右竖立着 n 块木板,从 1 到 n 依次编号,如下图所示。我们知道每块木板的高度,在第 n 块木板右侧竖立着一块高度无限大的木板,现对每块木板依次做如下的操作:对于第 i 块木板,我们从其右侧开始倒水,直到水的高度等于第 i 块木板的高度,倒入的水会淹没 a i a_i ai 块木板(如果木板左右两侧水的高度大于等于木板高度即视为木板被淹没)。求 n 次操作后,所有 a i a_i ai 的和是多少。
如图所示,在第 4 块木板右侧倒水,可以淹没第5 块和第6 块一共 2 块木板, a 4 a_4 a4 = 2。
现在你已经知道题意了,思考下,如果不考虑时间复杂度,该怎么写暴力程序呢?
暴力的时间复杂度是多少呢?在什么情况下可以用暴力程序呢?
如果要减小时间复杂度,该怎么优化暴力程序呢?有没有更高效的算法呢?
我们来分析下,什么时候水的高度会等于第 i 块木板的高度 h i h_i hi 呢,一定是水往右边漫延遇到了一块高度大于等于 h i h_i hi 的木板 j, a i a_i ai 就等于木板 i 和木板 j 之间的木板数。于是,问题就变成了寻找在第 i i i 个数右边第一个比它大的数。
我们可以暴力求解,从 1 循环到 n n n ,对每块木板再往右循环一遍,这样的时间复杂度是 O ( n 2 ) \mathcal{O}(n^2) O(n2) 的。有没有高效一点的做法呢?
我们回想下单调栈的性质,可以在某点左右扩展出一段连续区间,且该点在区间里始终保证是最值,和这题非常相似,而且这道题只要看点右侧扩展出来的区间即可。那么,接下来我们就用单调栈来解这道题。
#include
#include
#define ERROR 0
#define OK 1
typedef struct Node {
int id, height;
} Node;
typedef struct Stack {
Node *elements;
int max_size, top_index;
} Stack;
void init(Stack *s, int length) {
s->elements = (Node *)malloc(sizeof(Node) * length);
s->max_size = length;
s->top_index = -1;
}
int push(Stack *s, Node element) {
if (s->top_index >= s->max_size - 1) {
return ERROR;
}
s->top_index++;
s->elements[s->top_index] = element;
return OK;
}
int pop(Stack *s) {
if (s->top_index < 0) {
return ERROR;
}
s->top_index--;
return OK;
}
Node top(Stack *s) {
return s->elements[s->top_index];
}
int empty(Stack *s) {
if (s->top_index < 0) {
return 1;
} else {
return 0;
}
}
void clear(Stack *s) {
free(s->elements);
free(s);
}
int main() {
int n, ans = 0;
scanf("%d",&n);
Stack *stack = (Stack *)malloc(sizeof(Stack));
init(stack, n);
Node temp;
for (int i =1 ; i <= n; i++) {
scanf("%d",&temp.height);
temp.id = i;
while(!empty(stack) && top(stack).height <= temp.height) {
ans = ans + i - top(stack).id - 1;
pop(stack);
}
push(stack, temp);
}
while(!empty(stack)){
ans = ans + n + 1 - top(stack).id - 1;
pop(stack);
}
printf("%d\n",ans);
clear(stack);
return 0;
}