力扣解题-155. 最小栈

4 阅读6分钟

力扣解题-155. 最小栈

设计一个支持 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.

提示:

-2³¹ <= val <= 2³¹ - 1

pop、top 和 getMin 操作总是在 非空栈 上调用

push, pop, top, and getMin最多被调用 3 * 10⁴ 次

Related Topics

栈、设计


第一次解答

解题思路

核心方法:双栈法(数据栈+最小栈),通过两个栈分别存储“所有元素”和“当前栈内最小元素”,保证getMin()操作的时间复杂度为O(1),所有核心操作均满足常数时间要求,是本题的经典最优解法。

核心原理铺垫(双栈设计的合理性)

要实现“常数时间获取最小值”,需在元素入栈/出栈时同步维护最小值状态:

  • 数据栈(stack):存储所有入栈的元素,支持常规的push/pop/top操作;
  • 最小栈(minStack):存储“当前数据栈中的最小元素”,栈顶始终是当前数据栈的最小值;
  • 核心规则:入栈时同步更新最小栈,出栈时若弹出的是最小值则同步弹出最小栈的栈顶,保证最小栈与数据栈的状态一致。
核心逻辑拆解
  1. 初始化:创建两个空栈(stack存储数据,minStack存储最小值);
  2. push操作
    • 元素val压入数据栈;
    • 若最小栈为空,或val ≤ 最小栈栈顶元素(新元素更小/相等),将val压入最小栈;
    • (注:用而非<,避免相同最小值被重复弹出导致错误,如连续push多个-3时,minStack需存储多个-3);
  3. pop操作
    • 弹出数据栈栈顶元素val
    • val等于最小栈栈顶元素(说明弹出的是当前最小值),同步弹出最小栈栈顶;
    • 题目保证pop操作在非空栈调用,仍保留空栈异常处理增强健壮性;
  4. top操作:返回数据栈栈顶元素(非空校验+异常抛出);
  5. getMin操作:返回最小栈栈顶元素(非空校验+异常抛出)。
具体步骤(以示例1为例)
操作数据栈(stack)最小栈(minStack)getMin结果top结果
push(-2)[-2][-2]--
push(0)[-2, 0][-2]--
push(-3)[-2, 0, -3][-2, -3]--
getMin()[-2, 0, -3][-2, -3]-3-
pop()[-2, 0][-2]--
top()[-2, 0][-2]-0
getMin()[-2, 0][-2]-2-
性能说明
  • 时间复杂度:所有操作(push/pop/top/getMin)均为O(1)(栈的入栈/出栈/取栈顶都是常数时间),执行用时4ms击败100%用户;
  • 空间复杂度:O(n)(最坏情况所有元素都存入两个栈,如严格递减序列),内存消耗46.18MB击败66.45%用户;
  • 优势:
    1. 逻辑清晰,双栈分工明确,易于理解和维护;
    2. 异常处理增强代码健壮性,符合工业级开发规范;
    3. 最小栈仅存储最小值相关元素,空间开销可控。
    private Deque<Integer> stack;
    private Deque<Integer> minStack;
    public MinStack() {
        stack = new ArrayDeque<>();
        minStack = new ArrayDeque<>();
    }

    public void push(int val) {
        stack.push(val);
        if (minStack.isEmpty() || val <= minStack.peek()) {
            minStack.push(val);
        }
    }

    public void pop() {
        if(stack.isEmpty()){
            throw new RuntimeException("栈为空");
        }else {
            int val=stack.pop();
            if(val==minStack.peek()) {
                minStack.pop();
            }
        }

    }

    public int top() {
        if(!stack.isEmpty()) {
            return stack.peek();
        }else {
            throw new RuntimeException("栈为空");
        }
    }

    public int getMin() {
        if(!minStack.isEmpty()) {
            return minStack.peek();
        }else {
            throw new RuntimeException("栈为空");
        }
    }

示例解答

解题思路

解法1:单栈优化版(空间优化)

核心方法:单栈存储“元素+当前最小值”对,用一个栈替代两个栈,每个栈元素存储[当前值, 入栈时的最小值],在保证O(1)时间复杂度的同时,逻辑更紧凑(空间复杂度仍为O(n),但减少了一个栈的对象开销)。

核心设计思路
  • 栈中每个元素是长度为2的数组:[val, currentMin],其中currentMin表示将val入栈后,栈内的最小值;
  • push时计算当前最小值(对比val和栈顶的currentMin),将[val, currentMin]压入栈;
  • pop/top/getMin时直接操作栈顶数组的对应位置。
代码实现
import java.util.ArrayDeque;
import java.util.Deque;

class MinStack {
    private Deque<int[]> stack; // 每个元素为 {当前值, 当前最小值}

    public MinStack() {
        stack = new ArrayDeque<>();
    }

    public void push(int val) {
        if (stack.isEmpty()) {
            // 栈空时,当前最小值就是val
            stack.push(new int[]{val, val});
        } else {
            // 栈非空时,当前最小值 = min(栈顶最小值, val)
            int currentMin = Math.min(stack.peek()[1], val);
            stack.push(new int[]{val, currentMin});
        }
    }

    public void pop() {
        if (stack.isEmpty()) {
            throw new RuntimeException("栈为空");
        }
        stack.pop();
    }

    public int top() {
        if (stack.isEmpty()) {
            throw new RuntimeException("栈为空");
        }
        return stack.peek()[0];
    }

    public int getMin() {
        if (stack.isEmpty()) {
            throw new RuntimeException("栈为空");
        }
        return stack.peek()[1];
    }
}
优势说明
  • 时间复杂度:仍为O(1),与双栈法性能一致;
  • 空间复杂度:O(n)(每个元素存储两个整数,实际内存开销与双栈法接近);
  • 核心优势:
    1. 仅用一个栈,减少对象创建和维护的开销;
    2. 无需在pop时判断是否弹出最小值,逻辑更简洁;
    3. 天然支持重复最小值场景,无需处理的边界问题。
解法2:差值法(极致空间优化,进阶思路)

核心方法:单栈存储“当前值与最小值的差值”,用一个变量记录当前最小值,栈中存储val - min的差值,通过差值反向推导原值和新最小值,实现O(1)额外空间(栈本身的存储不计入)。

代码实现
import java.util.ArrayDeque;
import java.util.Deque;

class MinStack {
    private Deque<Long> stack; // 用Long避免差值溢出
    private long minVal;       // 当前最小值

    public MinStack() {
        stack = new ArrayDeque<>();
    }

    public void push(int val) {
        if (stack.isEmpty()) {
            minVal = val;
            stack.push(0L); // 差值为0
        } else {
            long diff = (long) val - minVal;
            stack.push(diff);
            // 差值<0说明val更小,更新最小值
            if (diff < 0) {
                minVal = val;
            }
        }
    }

    public void pop() {
        if (stack.isEmpty()) {
            throw new RuntimeException("栈为空");
        }
        long diff = stack.pop();
        // 差值<0说明弹出的是最小值,恢复上一个最小值
        if (diff < 0) {
            minVal = minVal - diff;
        }
    }

    public int top() {
        if (stack.isEmpty()) {
            throw new RuntimeException("栈为空");
        }
        long diff = stack.peek();
        // 差值<0:栈顶值就是当前minVal;否则:minVal + diff
        return diff < 0 ? (int) minVal : (int) (minVal + diff);
    }

    public int getMin() {
        if (stack.isEmpty()) {
            throw new RuntimeException("栈为空");
        }
        return (int) minVal;
    }
}
核心原理与适用场景
  • 空间优势:仅用一个变量存储最小值,栈存储差值,额外空间复杂度O(1)(栈本身的存储是必要开销);
  • 注意事项:
    1. Long存储差值,避免val=Integer.MIN_VALUEminVal=Integer.MAX_VALUE时差值溢出;
    2. 逻辑较复杂,需要反向推导原值和最小值,可读性低于双栈/单栈对法;
  • 适用场景:对内存要求极高的场景,如嵌入式开发或大数据量处理。

总结

  1. 双栈法(第一次解答):逻辑清晰、易于维护,所有操作O(1)时间复杂度,是工程首选的经典解法;
  2. 单栈对法:单栈存储“值+最小值”,逻辑紧凑,性能与双栈法持平,减少了一个栈的对象开销;
  3. 差值法:极致空间优化(O(1)额外空间),但逻辑复杂、有溢出风险,适合对内存要求极高的场景;
  4. 关键技巧:
    • 核心目标:通过“空间换时间”保证getMin()的O(1)时间复杂度;
    • 边界处理:重复最小值需用判断入栈,避免弹出时丢失最小值;
    • 健壮性:保留空栈异常处理,符合工业级代码规范。