O(1) 时间获取最小值的巧妙设计——力扣155.最小栈
力扣155.最小栈

【LeetCode 155】最小栈(Java 题解 + 三种实现方式详细对比)
一、题目描述
请你设计一个支持以下操作的栈(Stack):
push(int val):将元素压入栈。pop():删除栈顶元素。top():返回栈顶元素。getMin():返回栈中最小的元素。
要求:
所有操作的时间复杂度均为 O(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
三、题目分析
普通栈能在 O(1) 时间完成 push()、pop()、top() 操作。
但 getMin() 操作如果每次都线性扫描,复杂度将变为 O(n)。
为了在 O(1) 时间获取最小值,需要额外维护一个辅助结构。
核心思想:
在每次压入或弹出时,同时维护当前最小值。
四、解法一:双栈法(推荐)
思路:
使用两个栈:
- 一个普通栈
dataStack存放所有元素; - 一个辅助栈
minStack存放“每一步的最小值”。
规则:
-
每次
push(val)时:- 将
val压入dataStack; - 将
Math.min(val, 当前最小值)压入minStack。
- 将
-
每次
pop()时:- 两个栈同时弹出;
-
getMin()时:- 返回
minStack栈顶。
- 返回
代码实现:
import java.util.Stack;class MinStack {private Stack<Integer> dataStack; // 数据栈private Stack<Integer> minStack; // 最小值栈public MinStack() {dataStack = new Stack<>();minStack = new Stack<>();}public void push(int val) {dataStack.push(val);// 若 minStack 为空,则直接压入;否则压入两者中较小的值if (minStack.isEmpty()) {minStack.push(val);} else {minStack.push(Math.min(val, minStack.peek()));}}public void pop() {dataStack.pop();minStack.pop();}public int top() {return dataStack.peek();}public int getMin() {return minStack.peek();}
}

时间复杂度:
- 所有操作均为 O(1)。
空间复杂度:
- O(n),需要额外的 minStack。
五、解法二:单栈 + 辅助变量
思路:
只用一个栈和一个变量 min 保存当前最小值。
每次 push 时:
- 如果新元素小于当前
min,先把旧的min压入栈,再更新min。
每次 pop 时:
- 如果弹出的元素等于当前
min,说明最小值被弹出,需要恢复上一个最小值。
代码实现:
import java.util.Stack;class MinStack {private Stack<Integer> stack;private int min;public MinStack() {stack = new Stack<>();min = Integer.MAX_VALUE;}public void push(int val) {if (val <= min) {// 先保存旧的最小值stack.push(min);min = val;}stack.push(val);}public void pop() {if (stack.pop() == min) {// 恢复之前的最小值min = stack.pop();}}public int top() {return stack.peek();}public int getMin() {return min;}
}

优点:
- 不需要额外栈;
- 内存占用略小。
缺点:
- 逻辑略微复杂;
- 需要仔细处理 push 和 pop 的顺序。
六、解法三:链表节点法(扩展理解)
有时我们可以不使用 Stack 类,而是用自定义链表结构实现。
每个节点存储:
- 当前值
val - 当前最小值
min - 下一个节点
next
代码实现:
class MinStack {private Node head;private class Node {int val;int min;Node next;Node(int val, int min, Node next) {this.val = val;this.min = min;this.next = next;}}public void push(int val) {if (head == null) {head = new Node(val, val, null);} else {head = new Node(val, Math.min(val, head.min), head);}}public void pop() {head = head.next;}public int top() {return head.val;}public int getMin() {return head.min;}
}

优点:
- 不依赖 Stack;
getMin()始终 O(1)。
缺点:
- 不如前两种方法直观。
七、三种方法对比
| 方法 | 思路 | 额外空间 | 代码复杂度 | 推荐程度 |
|---|---|---|---|---|
| 双栈法 | dataStack + minStack | O(n) | 简洁清晰 | ★★★★★ |
| 单栈 + 辅助变量 | 栈内保存旧最小值 | O(n) | 稍复杂 | ★★★★☆ |
| 链表节点法 | 每节点保存当前最小值 | O(n) | 不依赖 Stack | ★★★☆☆ |
八、复杂度分析
| 操作 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| push() | O(1) | O(1) |
| pop() | O(1) | O(1) |
| top() | O(1) | O(1) |
| getMin() | O(1) | O(1) |
九、总结
最小栈设计的核心思想:
在每次操作时,维护当前“最小值”的状态,使得
getMin()可以在 O(1) 时间内完成。
重点记忆:
- 双栈法是最直观的面试写法;
- 单栈 + 辅助变量是空间优化思路;
- 链表法体现了数据结构设计的灵活性。
十、参考链接
- LeetCode 155. Min Stack 题目链接
