力扣 155. 最小栈:用辅助栈实现 O(1) 时间复杂度的 getMin

53 阅读3分钟

在日常开发中,我们经常会遇到需要快速获取当前数据结构中最小值的需求。如果只是简单地使用一个普通栈(Stack),那么每次调用 getMin() 都需要遍历整个栈来找到最小值,时间复杂度为 O(n) ,效率较低。

今天我们就来深入剖析一种经典且高效的解决方案——最小栈(Min Stack) ,并结合代码和逻辑图解,带你彻底理解如何通过「辅助栈」的设计思想,在常数时间内完成 getMin 操作!


🎯 问题描述

设计一个支持以下操作的栈:

  • push(x):将元素 x 压入栈顶。
  • pop():移除栈顶元素。
  • top():获取栈顶元素。
  • getMin():获取栈中最小元素。

要求:所有操作的时间复杂度均为 O(1)


💡 解题思路:辅助栈法

✅ 核心思想:两个栈

我们使用两个栈来实现这个功能:

  1. 主栈(stack) :存储所有实际压入的元素。
  2. 辅助栈(stack1) :只存储“可能成为最小值”的元素,即维护一个单调递减栈

🔍 关键点:辅助栈不是记录所有元素,而是记录每个阶段的最小值。


🔁 入栈逻辑

当新元素 val 入栈时:

if (this.stack1.length === 0 || val <= this.stack1[this.stack1.length - 1]) {
    this.stack1.push(val);
}
this.stack.push(val);
  • 如果辅助栈为空,或者 val 小于等于当前辅助栈顶(即比现有最小值还小或相等),就将 val 推入辅助栈。
  • 这样保证了辅助栈始终是非递增的(单调递减栈)。

⚠️ 注意:这里用了 <= 而不是 <,是为了处理重复最小值的情况。比如连续压入两个 3,我们要保留两个 3 在辅助栈中,否则出栈后无法正确恢复最小值。


🚫 出栈逻辑

const val = this.stack.pop();
if (val === this.stack1[this.stack1.length - 1]) {
    this.stack1.pop();
}
  • 先从主栈弹出元素。
  • 若该元素恰好等于辅助栈顶,则说明它是当前阶段的最小值,也需从辅助栈弹出。

✅ 这样能确保辅助栈永远保存的是对应时刻下的最小值。


🔍 查看栈顶与最小值

top() { return this.stack[this.stack.length - 1]; }
getMin() { return this.stack1[this.stack1.length - 1]; }
  • top() 返回主栈顶。
  • getMin() 直接返回辅助栈顶即可,因为辅助栈顶就是当前全局最小值。

🧠 示例演示

假设我们执行如下操作序列:

push(5)
push(3)
push(8)
push(1)
pop()
getMin()
操作主栈辅助栈
push(5)[5][5]
push(3)[5,3][5,3]
push(8)[5,3,8][5,3]
push(1)[5,3,8,1][5,3,1]
pop()[5,3,8][5,3]
getMin()——→ 3

可以看到,即使中间有更大的数插入,只要最小值发生变化,辅助栈就会更新;而删除时也能精准同步。


✅ 代码完整版(JavaScript)

var MinStack = function() {
    this.stack = [];
    this.stack1 = []; // 辅助栈,存储最小值
};

MinStack.prototype.push = function(val) {
    if (this.stack1.length === 0 || val <= this.stack1[this.stack1.length - 1]) {
        this.stack1.push(val);
    }
    this.stack.push(val);
};

MinStack.prototype.pop = function() {
    const val = this.stack.pop();
    if (val === this.stack1[this.stack1.length - 1]) {
        this.stack1.pop();
    }
};

MinStack.prototype.top = function() {
    return this.stack[this.stack.length - 1];
};

MinStack.prototype.getMin = function() {
    return this.stack1[this.stack1.length - 1];
};

📈 复杂度分析

操作时间复杂度空间复杂度
pushO(1)O(n)
popO(1)O(n)
topO(1)O(1)
getMinO(1)O(1)

✅ 所有操作均达到最优的 O(1),空间上最坏情况是 O(n),但平均情况下远低于此。


🎯 总结:为什么辅助栈这么牛?

“以空间换时间” 的极致体现

  • 主栈负责正常的数据进出;
  • 辅助栈则像一位“观察员”,默默记录每一个关键时刻的最小值;
  • 不依赖遍历,不浪费时间,真正实现了高效查询。