【码道初阶】Leetcode155踩坑最小栈问题:最小栈:算法对了,却输给了 Java 的 “==“?

【LeetCode 踩坑】最小栈:算法对了,却输给了 Java 的 "=="?

在做 LeetCode 155. 最小栈 时,我使用经典的"双栈法":一个栈 stack 存数据,另一个栈 minstack 存当前的最小值。

逻辑非常通顺:

  1. Push :如果新来的数小于等于 minstack 栈顶,同步压入 minstack。
  2. Pop :如果要弹出的数等于 minstack 栈顶,minstack 也要同步弹出。

代码写得行云流水,前面的测试用例也都过了,结果在提交时在第 25 个用例翻车了:
输入 :push(512), push(-1024)...
预期 :getMin() 返回 512
实际 :getMin() 返回 -1024

为什么前面的小数字(如 1, 2, 3)都没事,一旦出现 512 或 -1024 这种大数就出错了?

1. 案发现场

问题出在 pop() 操作的判断逻辑上:

java 复制代码
public void pop() {
    // ... 省略空判断
    
    // 💀【致命错误】这里使用了 == 来比较两个 Integer 对象
    if(stack.peek() == minstack.peek()) 
    {
        stack.pop();
        minstack.pop();
        return;
    }
    stack.pop();
}

2. 深度解析:Integer 的"骗局"

在 Java 中,泛型栈 Stack<Integer> 里存的不是基本数据类型 int,而是包装类 Integer 对象。

当我们写 stack.peek() == minstack.peek() 时,我们实际上是在比较两个对象的内存地址。

为什么小数字能通过?

Java 为了节省内存,设计了一个 Integer Cache(整数缓存池) 。默认范围是 -128 到 127。

  • 当你 push(100) 时,Java 从缓存池里拿了一个对象给你。
  • 两个栈里的 100 指向的是同一个内存地址。
  • 此时 obj1 == obj2 返回 true。逻辑正常执行。

为什么大数字(512, -1024)报错?

一旦数值超出了缓存范围(比如 -1024):

  • Java 会 new Integer(-1024)。
  • stack 里的 -1024 是对象 A。
  • minstack 里的 -1024 是对象 B。
  • 虽然它们的值一样,但 地址 A != 地址 B。
  • 判定条件 == 返回 false。

后果 :

代码认为要弹出的不是最小值,于是只执行了 stack.pop(),保留了 minstack 里的值。

导致 minstack 里残留了一个本该被删除的最小值(-1024),当栈里只剩下 512 时,getMin() 依然错误的返回了这个幽灵值 -1024。

3. 避坑指南

修正方案 A:使用 .equals()(推荐)

这是比较对象数值的标准做法。

java 复制代码
// 修正后
if(stack.peek().equals(minstack.peek())) {
    stack.pop();
    minstack.pop();
} else {
    stack.pop();
}

修正方案 B:拆箱比较

强制转换成 int 基本类型,触发自动拆箱,这样 == 比较的就是数值了。

java 复制代码
// 虽然可行,但写法略繁琐
if(stack.peek().intValue() == minstack.peek().intValue())

4. 优化后的完整代码

除了修复 Bug,我还稍微优化了一下 pop 的逻辑结构,使其更简洁:

java 复制代码
class MinStack {
    // 使用 Deque 替代 Stack 是更现代的 Java 写法,不过 Stack 也没问题
    Stack<Integer> stack;
    Stack<Integer> minStack;

    public MinStack() {
        stack = new Stack<>();
        minStack = new Stack<>();
    }
    
    public void push(int val) {
        stack.push(val);
        // 注意这里要有等于号,处理重复最小值的情况
        if(minStack.isEmpty() || val <= minStack.peek()) {
            minStack.push(val);
        }
    }
    
    public void pop() {
        if(stack.isEmpty()) return;
        
        // 1. 先弹出数据栈的元素,拿到它的值
        Integer popVal = stack.pop();
        
        // 2. 检查这个值是否等于最小栈顶
        // ⚠️ 使用 .equals() 避免大数判错
        if(!minStack.isEmpty() && popVal.equals(minStack.peek())) {
            minStack.pop();
        }
    }
    
    public int top() {
        return stack.peek();
    }
    
    public int getMin() {
        return minStack.peek();
    }
}

5. 总结

这道题其实是披着算法皮的语言基础题。

  • 算法层面:考察辅助栈的思想(空间换时间)。
  • 语言层面 :考察对 Java 包装类、对象比较 (== vs .equals) 以及 Integer 缓存机制的理解。

记住一句话:在 Java 中比较对象的值,永远首选 .equals(),除非你明确知道自己在比较内存地址!

相关推荐
学编程就要猛几秒前
Spring AI Alibaba(上)
java·人工智能·spring·springaialibaba
stolentime7 分钟前
AT_arc182_a [ARC182A] Chmax Rush!题解
数据结构·c++·算法·动态规划
longlongzihan8 分钟前
LeetCode 189. 轮转数组:从直观模拟到最优原地算法
c++·算法·leetcode
爱喝热水的呀哈喽13 分钟前
cpp二分库函数 lambda捕获
算法
imDwAaY14 分钟前
Spring Boot的核心配置文件一览及其加载顺序
java·spring boot·后端
zhangx1234_16 分钟前
javaEE 多线程1
java·linux·服务器
鬼手点金21 分钟前
opencode-crawl4ai使用建议
java·开发语言·人工智能·python·机器学习
longlongzihan24 分钟前
LeetCode 76. 最小覆盖子串:滑动窗口算法详解
算法·leetcode·滑动窗口
专业程序开发源35 分钟前
springboot校园志愿者管理系统28562-计算机课程设计、毕业设计
java·spring boot·后端·python·django·php·课程设计
thefool11226639 分钟前
多线程 2
java