详细介绍: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 + minStackO(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) 时间内完成。

重点记忆:

  1. 双栈法是最直观的面试写法;
  2. 单栈 + 辅助变量是空间优化思路;
  3. 链表法体现了数据结构设计的灵活性。

十、参考链接


posted @ 2026-02-10 13:49  gccbuaa  阅读(10)  评论(0)    收藏  举报