单调栈
- 栈和队列的区别在于,一般情况下,队列可以在两端进出元素,而栈只能由一端进出元素
- 本质上,单调队列的队首禁止进出元素,只能从队尾进出元素时,就变成了一个单调栈
- 如下图所示,将数组元素依次压入单调栈中
- 当紫色元素入栈时,蓝色线条左侧元素均大于紫色元素,黄色元素均小于紫色元素
- 为保持栈内元素
单调递减的性质,需要将黄色元素全部出栈,此时- 在
原数组中的紫色元素左侧第一个比自己大的元素为绿色 a 元素 - 在
原数组中的黄色元素们右侧第一个比自己大的元素为紫色元素
- 在
-
上面是单调栈的入栈过程,下面总结一下,单调队列与单调栈的性质
- 单调队列,擅长维护区间最值,若维护的是区间最小值,则队列是单调递增的
- 单调栈,擅长维护集合中的元素的最近大小关系
-
从左侧入栈时,维护的是左侧的最近关系
-
从右侧入栈时,维护的是右侧的最近关系
-
-
下面通过代码演示单调栈的能力,现给出数组 list,求原数组中每个元素,左侧最近的比自己小的元素,右侧最近的比自己小的元素
- 下面代码中 stack 为单调递减栈的数据区域,存放的是原数组中元素的下标值
- prev 为存放原数组中,对应元素左侧的第一个比自己小的元素的下标值
- next 为存放原数组中,对应元素右侧的第一个比自己小的元素的下标值
- 首先将所有元素依次入栈,同时维护栈内元素的单调性,将不符合单调栈规则的元素进行 pop 操作,同时,被出栈的元素右侧比他们更小的且最近的元素在原数组中的下标值 i
- 一轮操作后,若此时的栈已空,则表示 i 元素左侧没有比它小更小的元素,可以使用虚拟元素索引 -1 表示更小元素索引,否则栈顶元素就是当前元素 i 左侧最近的更小元素,然后将 i 入栈即可
- 所有元素完成入栈,表示栈内剩余的元素右侧没有比之更小的元素了,可以使用虚拟元素索引 list.length 表示更小元素的索引值
/**
* @param {*} list 原数组
*/
const ascStack = (list) => {
const stack = [];
const prev = [];
const next = [];
for (let i = 0; i < list.length; i++) {
while (stack.length && list[stack[stack.length - 1]] > list[i]) {
const ind = stack.pop();
next[ind] = i;
}
if (!stack.length) prev[i] = -1;
else prev[i] = stack[stack.length - 1];
stack.push(i); // i 入栈
}
while (stack.length) {
next[stack.pop()] = list.length;
}
console.log(`
${list.join(' ')} \n
${prev.join(' ')} \n
${next.join(' ')} \n
`);
};
ascStack([6, 7, 9, 0, 8, 3, 4, 5, 1, 2]);
- 上述代码输出如下
// output:
// list: 6 7 9 0 8 3 4 5 1 2
// ind: -1 0 1 2 3 4 5 6 7 8 9 10
// prev: -1 0 1 -1 3 3 5 6 3 8
// next: 3 3 3 10 5 8 8 8 10 10
小结
- 本文介绍了单调栈的特点及其代码示例,并对比总结了单调栈与单调队列的差异特点
“ 本文正在参加「金石计划 . 瓜分6万现金大奖」 ”