最小栈:基于辅助栈的高效实现

58 阅读4分钟

最小栈:基于辅助栈的高效实现

在栈的常规操作中,获取栈内最小值是一个常见需求。如果每次调用 “获取最小值” 方法时都遍历栈,时间复杂度会达到 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)

五、总结

最小栈的核心优化思路是 “空间换时间”,通过辅助栈(单调栈)记录最小值的变化轨迹,避免了每次获取最小值时的遍历操作。这种设计既保留了栈的基础操作特性,又满足了高效获取最小值的需求,是算法设计中 “单调栈” 思想的典型应用。

关键点回顾:

  1. 辅助栈的核心是 “同步维护最小值”,入栈 / 出栈时需保证与主栈的最小值一致;
  2. 辅助栈的单调性(非递增)是实现 O (1) 获取最小值的核心;
  3. 相比遍历法,辅助栈以少量空间开销换取了时间效率的质的提升。