最小栈:基于辅助栈的高效实现
在栈的常规操作中,获取栈内最小值是一个常见需求。如果每次调用 “获取最小值” 方法时都遍历栈,时间复杂度会达到 O (n),在高频调用场景下效率较低。本文将介绍一种基于辅助栈(单调栈) 的最小栈实现方案,能将获取最小值的时间复杂度优化至 O (1),同时保证入栈、出栈、获取栈顶元素的操作仍为 O (1)。
一、最小栈的核心需求
最小栈需要支持栈的基础操作(push 入栈、pop 出栈、top 获取栈顶元素),同时额外提供getMin方法以常数时间获取当前栈内的最小值。
常规思路(遍历找最小值)的问题在于:每次getMin都要遍历整个栈,数据量较大时性能瓶颈明显。以下是这种低效实现的代码示例(ES5 构造函数风格):
javascript
运行
// 低效版:getMin遍历栈,时间复杂度O(n)
const MiniStack = function () {
this.stack = []; // 存储栈元素的主栈
}
// 入栈
MiniStack.prototype.push = function(x) {
this.stack.push(x);
}
// 出栈
MiniStack.prototype.pop = function() {
return this.stack.pop();
}
// 获取栈顶元素
MiniStack.prototype.top = function() {
if (!this.stack || !this.stack.length) {
return;
}
return this.stack[this.stack.length - 1];
}
// 获取最小值:遍历整个栈
MiniStack.prototype.getMin = function() {
let minValue = Infinity; // 初始化为无穷大
const { stack } = this;
for(let i = 0; i < stack.length; i++){
if(stack[i] < minValue){
minValue = stack[i];
}
}
return minValue;
}
这种实现虽然简单,但getMin的时间复杂度为 O (n),无法满足高性能场景的需求。
二、辅助栈(单调栈)解决方案
核心思路:维护两个栈,主栈存储所有元素,辅助栈(单调栈)仅存储 “当前栈内的最小值” 相关元素,保证辅助栈的栈顶始终是当前主栈的最小值。
1. 辅助栈的设计规则
- 入栈规则:当新元素入栈时,若辅助栈为空,或新元素小于等于辅助栈栈顶元素,则将新元素压入辅助栈(保证辅助栈栈顶是当前最小值)。
- 出栈规则:当主栈元素出栈时,若出栈元素等于辅助栈栈顶元素,则辅助栈也弹出栈顶元素(保证辅助栈与主栈的最小值同步)。
- 获取最小值:直接返回辅助栈的栈顶元素(O (1) 时间)。
2. 完整实现代码
javascript
运行
// 高效版:辅助栈实现最小栈,所有操作O(1)
const MiniStack = function() {
this.stack = []; // 主栈:存储所有元素
this.stack2 = []; // 辅助栈:存储当前最小值(单调栈)
}
/**
* 入栈操作
* @param {number} x - 入栈元素
*/
MiniStack.prototype.push = function(x) {
this.stack.push(x);
// 辅助栈为空,或新元素≤辅助栈栈顶(保证栈顶是当前最小值)
if (this.stack2.length === 0 || this.stack2[this.stack2.length - 1] >= x) {
this.stack2.push(x);
}
}
/**
* 出栈操作
* @returns {number|undefined} 出栈元素
*/
MiniStack.prototype.pop = function() {
// 若主栈出栈元素等于辅助栈栈顶,辅助栈同步出栈
if (this.stack.pop() === this.stack2[this.stack2.length - 1]) {
this.stack2.pop();
}
}
/**
* 获取栈顶元素
* @returns {number|undefined} 栈顶元素
*/
MiniStack.prototype.top = function() {
return this.stack[this.stack.length - 1];
}
/**
* 获取当前栈内最小值(O(1))
* @returns {number|undefined} 最小值
*/
MiniStack.prototype.getMin = function() {
return this.stack2[this.stack2.length - 1];
}
三、核心优势与原理分析
1. 时间复杂度
- push、pop、top、getMin 所有操作的时间复杂度均为 O (1),相比遍历法的 O (n),性能大幅提升。
2. 空间复杂度
- 最坏情况下(栈内元素严格递减),辅助栈会存储所有元素,空间复杂度为 O (n);但平均场景下,辅助栈仅存储少量最小值元素,空间开销可控。
3. 单调性保证
辅助栈是单调非递增栈(栈内元素从栈底到栈顶不递增),因此栈顶始终是当前主栈的最小值,这是实现 O (1) 获取最小值的关键。
四、使用示例
javascript
运行
// 实例化最小栈
const minStack = new MiniStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
console.log(minStack.getMin()); // 输出:-3(当前最小值)
minStack.pop();
console.log(minStack.top()); // 输出:0(栈顶元素)
console.log(minStack.getMin()); // 输出:-2(弹出-3后,最小值变为-2)
五、总结
最小栈的核心优化思路是 “空间换时间”,通过辅助栈(单调栈)记录最小值的变化轨迹,避免了每次获取最小值时的遍历操作。这种设计既保留了栈的基础操作特性,又满足了高效获取最小值的需求,是算法设计中 “单调栈” 思想的典型应用。
关键点回顾:
- 辅助栈的核心是 “同步维护最小值”,入栈 / 出栈时需保证与主栈的最小值一致;
- 辅助栈的单调性(非递增)是实现 O (1) 获取最小值的核心;
- 相比遍历法,辅助栈以少量空间开销换取了时间效率的质的提升。