【LeetCode 踩坑】最小栈:算法对了,却输给了 Java 的 "=="?
在做 LeetCode 155. 最小栈 时,我使用经典的"双栈法":一个栈 stack 存数据,另一个栈 minstack 存当前的最小值。
逻辑非常通顺:
- Push :如果新来的数小于等于
minstack栈顶,同步压入minstack。 - 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(),除非你明确知道自己在比较内存地址!