🎬 第一幕:小哆啦的栈塔挑战
力扣大陆的天空,悬浮着一座高耸的"栈塔"。塔门口,一行火红的文字闪烁:
"想进塔顶,先回答这个问题:
设计一个能 随时告诉我最小值 的栈,所有操作必须 O(1) ,包括
push
、pop
、top
、getMin
。"
小哆啦眼睛一亮:
"这不就是普通栈 +
Math.min
吗?小菜一碟!"
于是他写下了第一版代码:
typescript
class MinStack {
private stack: number[] = [];
push(val: number) { this.stack.push(val); }
pop() { this.stack.pop(); }
top() { return this.stack[this.stack.length - 1]; }
getMin() { return Math.min(...this.stack); } // 看,多优雅!
}
他兴冲冲按下运行按钮,力扣精灵冷冷吐出一句话:
"
getMin()
时间复杂度 O(n),你确定敢带这代码进塔?"
小哆啦吓了一跳:
"哎呀,我每次
Math.min
都要遍历一遍栈,确实慢到怀疑人生。"
他第一次意识到:
- 栈操作可以 O(1),但
getMin
每次 O(n),整体性能差到爆炸。 - 如果有百万次
getMin
,程序直接凉凉。
🧠 第二幕:为什么 O(1) 难住了小哆啦?
小哆啦在树下沉思:
"
push
、pop
好办,但getMin
怎么做到 O(1) 呢?如果能提前记住最小值就好了,可问题是------最小值可能会被 pop 掉!"
哆啦A梦递过一本秘籍,封面写着:
《辅助栈:记录历史最低点》
秘籍核心思想:
每次 push,一个栈存当前值,另一个栈同步记录当前最小值的"快照"。
pop 时两个栈一起退,保证最小值信息不会乱。
举个例子:
push 5 → minStack = [5]
push 3 → minStack = [5, 3]
push 7 → minStack = [5, 3, 3]
为什么第三次还是 3?因为 7 比 3 大,最小值还是 3,所以复制一遍最小值。
⚔️ 第三幕:魔法代码诞生
kotlin
class MinStack {
private stack: number[] = [];
private minStack: number[] = [];
push(val: number): void {
this.stack.push(val);
const minVal = this.minStack.length === 0 ? val : Math.min(val, this.minStack[this.minStack.length - 1]);
this.minStack.push(minVal);
}
pop(): void {
this.stack.pop();
this.minStack.pop();
}
top(): number {
return this.stack[this.stack.length - 1];
}
getMin(): number {
return this.minStack[this.minStack.length - 1];
}
}
✅ 每次 push,记录历史最小值快照
✅ getMin → 直接取辅助栈末尾,O(1)
✅ pop → 两个栈同步退位,状态永远一致
🏆 第四幕:空间换时间的哲学
小哆啦写下心得:
- 以前:每次
getMin
扫一遍 → 时间爆炸 - 现在:每次 push 多存一个数 → 空间略大,但性能飞升
- 典型的"空间换时间",就像哆啦A梦的口袋,随手一个道具换取无限可能。
🧩 第五幕:进阶优化------如何压缩空间?
突然,胖虎冷笑:
"你这辅助栈太胖了!能不能减肥?"
小哆啦灵机一动:
"其实没必要在 minStack 复制所有最小值,只在遇到 更小值 时才 push,pop 时如果 top 是当前最小值,再 pop 一次。"
优化版代码:
kotlin
class MinStack {
private stack: number[] = [];
private minStack: number[] = [];
push(val: number): void {
this.stack.push(val);
if (this.minStack.length === 0 || val <= this.minStack[this.minStack.length - 1]) {
this.minStack.push(val);
}
}
pop(): void {
const val = this.stack.pop();
if (val === this.minStack[this.minStack.length - 1]) {
this.minStack.pop();
}
}
top(): number {
return this.stack[this.stack.length - 1];
}
getMin(): number {
return this.minStack[this.minStack.length - 1];
}
}
✅ 空间优化成功:minStack
只存最小值的历史节点
✅ 仍然保持 O(1)
查询
✅ 压缩内存,胖虎终于满意
🎯 栈 VS 辅助栈对比总结
方法 | getMin复杂度 | 空间 | 可维护性 |
---|---|---|---|
暴力遍历 | O(n) | O(1) | 简单但低效 |
辅助栈 | O(1) | O(n) | 稳定快速 |
空间压缩版 | O(1) | O(k) (k ≪ n) | 进阶最优 |
🏁 终章:哲学彩蛋
小哆啦仰望栈塔:
"原来,辅助栈就像人生的备份系统,帮你记住最低谷的每一步。
只有记住自己的'最小值',你才能在关键时刻快速反应。"