155. 最小栈

155. 最小栈

题目链接(中等)

题目描述

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

实现 MinStack 类:

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

数据范围:

  • -2^31 <= val <= 2^31 - 1
  • pop、top 和 getMin 操作总是在 非空栈 上调用
  • push、pop、top、getMin 最多被调用 3 * 10^4 次

示例

输入:

["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.

核心思路

难点在于:普通栈的 push、pop、top 都是 $O(1)$,但 getMin 要 $O(1)$。

关键观察:栈是后进先出的。如果元素 a 在入栈时,栈里还有 b, c, d,那么只要 a 还在栈里,b, c, d 就一定也在栈里(因为 a 不弹出之前,它下面那些元素不会被弹出)。

推论:每个元素入栈时,栈内的最小值是确定的。只要记录下这个最小值,将来这个元素成为栈顶时,就可以直接返回当时记录的最小值。

因此可以用一个辅助栈,与元素栈同步插入和删除,专门存「当前元素对应的最小值」。


方法一:辅助栈

思路及解法

维护两个栈:

  • stack:存所有元素;
  • min_stack:存「每个元素入栈时,栈内的最小值」。

两个栈长度始终相同,操作一一对应:

  • push(x):
    • 元素栈压入 x;
    • 辅助栈压入 min(x, min_stack[-1]),即「当前元素和之前的最小值中更小的那个」;
  • pop():两个栈同时弹出栈顶;
  • top():返回 stack[-1];
  • getMin():返回 min_stack[-1],即当前栈内最小值。

辅助栈初始化:为了让 push 时 min_stack[-1] 总有值,可以放一个 inf(正无穷)在辅助栈底部,或者用一个标志位处理「空栈」情况。

举例:依次 push -2, 0, -3

操作 stack min_stack
push(-2) [-2] [inf, -2]
push(0) [-2, 0] [inf, -2, -2]
push(-3) [-2, 0, -3] [inf, -2, -2, -3]
pop() [-2, 0] [inf, -2, -2]
top() 返回 0 —
getMin() — 返回 -2

可以看到,辅助栈每个位置都存着「到该位置为止栈内的最小值」,与元素栈一一对应。

代码

import math

class MinStack:
    def __init__(self):
        self.stack = []
        self.min_stack = [math.inf]      # 哨兵,避免空栈判断

    def push(self, val: int) -> None:
        self.stack.append(val)
        self.min_stack.append(min(val, self.min_stack[-1]))

    def pop(self) -> None:
        self.stack.pop()
        self.min_stack.pop()

    def top(self) -> int:
        return self.stack[-1]

    def getMin(self) -> int:
        return self.min_stack[-1]

复杂度分析

  • 时间复杂度:所有操作均为 $O(1)$,每个操作最多调用两次栈的插入/删除。
  • 空间复杂度:$O(n)$,两个栈最坏情况下都存 $n$ 个元素。

方法二:单栈存差值(不用辅助栈)

思路及解法

用一个栈保存「元素与当前最小值的差值」,再用一个变量 min_val 记录当前最小值。

  • push(x):
    • 若栈为空:min_val = x,压入 0;
    • 否则压入 x - min_val,若 x < min_val 则更新 min_val = x;
  • pop():弹出差值 d:
    • 若 d >= 0,说明弹出的是普通元素,最小值不变;
    • 若 d < 0,说明弹出的元素就是当时的最小值,需要还原:min_val = min_val - d;
  • top():
    • 若栈顶差值 d >= 0,返回 min_val + d;
    • 若 d < 0,说明栈顶就是 min_val,直接返回 min_val;
  • getMin():返回 min_val。

优点:只用一个栈,空间省一半。
缺点:差值可能溢出(Python 无整数溢出,但 C++/Java 需要注意),逻辑略绕。

代码

class MinStack:
    def __init__(self):
        self.stack = []      # 存 x - min_val
        self.min_val = 0

    def push(self, val: int) -> None:
        if not self.stack:
            self.min_val = val
            self.stack.append(0)
        else:
            self.stack.append(val - self.min_val)
            if val < self.min_val:
                self.min_val = val

    def pop(self) -> None:
        d = self.stack.pop()
        if d < 0:
            # 弹出的就是当时的最小值,恢复
            self.min_val -= d

    def top(self) -> int:
        d = self.stack[-1]
        return self.min_val + d if d >= 0 else self.min_val

    def getMin(self) -> int:
        return self.min_val

复杂度分析

  • 时间复杂度:所有操作 $O(1)$。
  • 空间复杂度:$O(n)$,只用一个栈。

两种方法对比

方法 时间 空间 特点
辅助栈 $O(1)$ $O(n)$ 思路最直观,代码最短,面试首选
单栈存差值 $O(1)$ $O(n)$ 空间省一半,但逻辑绕,有溢出风险

推荐:

  • 面试:先写辅助栈,思路清晰、代码简洁,面试官一眼能看懂;
  • 进阶:可以提一下「单栈存差值」的思路,展示对空间优化的理解。

关键细节

1. 为什么辅助栈能同步弹出

辅助栈和元素栈长度始终相同,且「辅助栈的第 i 个元素」就是「元素栈前 i 个元素中的最小值」。因此:

  • 元素栈压入时,辅助栈也压入;
  • 元素栈弹出时,辅助栈也弹出;
  • 元素栈栈顶时,辅助栈栈顶就是当前最小值。

两者一一对应,永远不会错位。

2. 用 math.inf 做哨兵的技巧

self.min_stack = [math.inf]

在辅助栈底部放一个 inf,好处是 push 时不需要特判空栈:

self.min_stack.append(min(val, self.min_stack[-1]))

否则要写:

if not self.min_stack:
    self.min_stack.append(val)
else:
    self.min_stack.append(min(val, self.min_stack[-1]))

用一个哨兵,代码干净很多。

3. 单栈差值法的还原公式

为什么弹出负差值时 min_val -= d?

设当前最小值为 min_val,压入的新元素 x < min_val,压入的差值为 d = x - min_val < 0。
弹出时,我们要把 min_val 恢复为 x:

所以 min_val -= d。


总结

  • 核心观察:栈是后进先出,只要 a 在栈里,a 入栈时的「历史最小值」就是当前栈内最小值;
  • 辅助栈:与元素栈同步增删,每个位置存「到该位置为止的最小值」;
  • 关键操作:
    • push:辅助栈压入 min(x, min_stack[-1]);
    • pop:两栈同步弹出;
    • getMin:返回辅助栈栈顶;
  • 哨兵技巧:辅助栈底部放 math.inf,省掉空栈判断;
  • 进阶思路:单栈存差值,空间省一半,但有溢出风险。

相关题目

  • LC 716. 最大栈(要求同时支持最大值的栈)
  • LC 剑指 Offer 30. 包含 min 函数的栈(本题的简化版本)

155. 最小栈
https://mingsm17518.github.io/2026/10/08/刷题笔记/Hot100/栈/155. 最小栈/
作者
Ming
发布于
2026年10月8日
更新于
2026年10月8日
许可协议