在面试或日常算法练习中,栈(Stack) 是最基础的数据结构之一。但在实际应用中,我们经常遇到一个特殊需求:如何在 的时间复杂度内,随时获取当前栈中的最小值?
本文将结合 JavaScript 代码,通过两种方案的对比,带你深入理解“辅助栈”的设计思想。
1. 基础方案:遍历查找()
最直观的想法是在需要最小值时,遍历整个栈来寻找。
代码实现 (1.js)
在 1.js 中,我们定义了一个基础栈,其 getMin 方法如下:
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都需要遍历一次栈,时间复杂度为 。如果栈内数据量巨大,性能会显著下降。
2. 进阶方案:辅助栈()
为了将时间复杂度降到常数级,我们可以采用空间换时间的策略:引入一个辅助栈(stack2),专门用于实时存储当前状态下的最小值。
核心逻辑 (readme.md)
- 入栈 (
push) :判断入栈元素是否小于等于辅助栈顶。如果是,说明发现了一个更小(或相等)的最小值,将其也放入辅助栈。 - 出栈 (
pop) :判断主栈弹出的元素是否等于辅助栈顶。如果是,说明当前的最小值被移除了,辅助栈也需要同步弹出。 - 获取最小值 (
getMin) :辅助栈的栈顶永远是主栈中存在的最小值。
代码实现 (2.js)
const MinStack = function () {
this.stack = [];
this.stack2 = []; // 辅助栈:单调递减
}
MinStack.prototype.push = function (x) {
this.stack.push(x);
// 只有当辅助栈为空,或者新元素 <= 辅助栈顶时,才入辅助栈
if (this.stack2.length === 0 || this.stack2[this.stack2.length - 1] >= x) {
this.stack2.push(x);
}
}
MinStack.prototype.pop = function () {
// 如果主栈弹出的正好是当前的最小值,辅助栈同步弹出
if (this.stack.pop() === this.stack2[this.stack2.length - 1]) {
this.stack2.pop();
}
}
MinStack.prototype.getMin = function () {
// 辅助栈顶即为最小值,时间复杂度 O(1)
return this.stack2[this.stack2.length - 1];
}
3. 深度对比
| 特性 | 方案一:遍历法 (1.js) | 方案二:辅助栈 (2.js) |
|---|---|---|
| getMin 时间复杂度 | ||
| 空间复杂度 | (除原栈外) | (最坏情况下辅助栈等大) |
| 核心思想 | 动态计算 | 预处理 / 空间换时间 |
| 适用场景 | 内存极度受限,查询频率低 | 高频查询最小值 |
总结
辅助栈的设计其实是一种单调栈思想的变体。通过维护一个非严格单调递减的栈,我们确保了在任何时刻,最小值都能在 时间内被“信手拈来”。
在实际开发中,如果对查询性能有要求,方案二显然是更优的选择。
源码
1.js
const MiniStack = function () {
this.stack = [];
}
MiniStack.prototype.push = function (val) {
this.stack.push(val);
}
MiniStack.prototype.pop = function () {
if (this.stack.length === 0) return null;
return this.stack.pop();
}
MiniStack.prototype.top = function () {
if (this.stack.length === 0) return null;
return this.stack[this.stack.length - 1];
}
// O(n)
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;
}
2.js
const MinStack = function () {
this.stack = [];
this.stack2 = [];
}
MinStack.prototype.push = function (x) {
this.stack.push(x);
if (this.stack2.length === 0 || this.stack2[this.stack2.length - 1] >= x) {
this.stack2.push(x);
}
}
MinStack.prototype.pop = function () {
if (this.stack.length === 0) return null;
if (this.stack.pop() === this.stack2[this.stack2.length - 1]) {
this.stack2.pop();
}
}
MinStack.prototype.top = function () {
if (this.stack.length === 0) return null;
return this.stack[this.stack.length - 1];
}
MinStack.prototype.getMin = function () {
if (this.stack2.length === 0) return null;
return this.stack2[this.stack2.length - 1];
}