【栈】155. 最小栈

0 阅读3分钟

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

实现 MinStack 类:

  • MinStack() 初始化堆栈对象。
  • void push(int val) 将元素val推入堆栈。
  • void pop() 删除堆栈顶部的元素。
  • int top() 获取堆栈顶部的元素。
  • int getMin() 获取堆栈中的最小元素。

示例 1:

输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]

输出:
[null,null,null,null,-3,null,0,-2]

解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   --> 返回 -3.
minStack.pop();
minStack.top();      --> 返回 0.
minStack.getMin();   --> 返回 -2.

提示:

  • -231 <= val <= 231 - 1
  • poptop 和 getMin 操作总是在 非空栈 上调用
  • pushpoptop, and getMin最多被调用 3 * 104 次

1. 脑洞生活案例:你的“前任打分笔记”

想象你是一个记性不太好,但非常严谨的人。你每谈一个新对象,都会在笔记本上记录他们的长相分数。

  • 普通记事本(普通栈): 你按顺序记录:8分、6分、9分、5分。
  • 你的核心需求: 任何时候,如果闺蜜问你:“你谈过的所有对象里,最丑的一个是多少分? ” 你必须秒回,不能翻书找半天。

你的绝招: 你除了带个**“记录本” ,手里还攥着一个“扎心小纸条”**。

  1. 第一个对象 8 分:本子上记 8,小纸条写上 8(目前最差)。
  2. 第二个对象 6 分:本子上记 6。比纸条上的 8 还丑?赶紧在纸条上叠写一个 6。
  3. 第三个对象 9 分:本子上记 9。没纸条上的 6 丑?别理它,纸条上再写一个 6(为了保持同步,防止分手时乱套)。
  4. 第四个对象 5 分:本子上记 5。比 6 还丑!纸条上赶紧写个 5。

当分手(Pop)时: 如果你和最后一个(5分那个)分手了,你就把本子最后一页撕了,同时把纸条最上面那个 5 也撕了。 结果: 纸条露出来的最上面那个数字,永远是剩下的人里最丑的!


2. 代码实现(JavaScript)

在代码里,我们就准备两个数组:一个存所有数据(本子),一个存当前最小值(扎心小纸条)。

JavaScript

var MinStack = function() {
    this.stack = [];      // 普通本子:记录所有人
    this.minStack = [];   // 扎心小纸条:记录历史最丑
};

/** * @param {number} val
 * @return {void}
 */
MinStack.prototype.push = function(val) {
    this.stack.push(val);
    
    // 如果小纸条是空的,或者新来的比纸条上最上面的还丑(更小)
    // 我们就在纸条上记下这个新的最小值;否则,就把旧的最小值再复制一遍顶上去
    if (this.minStack.length === 0 || val <= this.getMin()) {
        this.minStack.push(val);
    } else {
        this.minStack.push(this.getMin());
    }
};

/**
 * @return {void}
 */
MinStack.prototype.pop = function() {
    this.stack.pop();
    this.minStack.pop(); // 两个本子同步撕掉,保证一一对应
};

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

/**
 * @return {number}
 */
MinStack.prototype.getMin = function() {
    return this.minStack[this.minStack.length - 1];
};

3. 方法回顾

这个解法的核心在于:空间换时间

  • 为什么要两个栈? 如果你只用一个变量存最小值,万一那个最小值被 pop 掉了,你就不知道第二小的是谁了。
  • 同步逻辑: 这就好比影分身。主身(stack)去闯荡江湖,影子(minStack)专门负责帮他记着“截止到目前为止,你见过最怂的时刻”。主身消失一秒,影子也跟着消失一秒。