面试知识库

155. 最小栈#

今天我们来深入探讨一道非常精彩的数据结构设计题 - LeetCode 155「最小栈」。这道题不仅考察了栈的基本操作,更蕴含着优化思维的艺术。

📚 从实际场景理解#

想象你是一位股票交易员,需要随时跟踪当前持仓中的最低价格股票。每次买入新股票或卖出旧股票后,都要能立即知道组合中的最低价。这个场景就很像我们今天要解决的最小栈问题!

问题描述#

题目目标#

设计一个支持 push、pop、top 和 getMin 操作的栈,并保证每个操作都能在 O(1) 时间复杂度内完成。

示例 1#

输入:

["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]
text

输出: [null,null,null,null,-3,null,0,-2] 说明: 把输入和输出放在一起看,会更容易直接抓住题目要求的变化。

💡 问题解析#

题目要求: 设计一个支持 push、pop、top 和 getMin 操作的栈数据结构:

  1. push(x) —— 将元素 x 推入栈中
  2. pop() —— 删除栈顶的元素
  3. top() —— 获取栈顶元素
  4. getMin() —— 检索栈中的最小元素

所有操作必须在 O(1) 时间复杂度内完成。

示例:

MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin();   // 返回 -3
minStack.pop();
minStack.top();      // 返回 0
minStack.getMin();   // 返回 -2
java

🤔 思维发展过程#

1. 初学者思路#

很多人的第一反应是:用一个变量记录最小值。但这种方法在pop操作后无法知道新的最小值。

2. 进阶思路#

可以在每次push时都遍历栈找最小值,但这样getMin的时间复杂度就变成了O(n)。

3. 最优思路#

使用辅助栈同步记录当前状态下的最小值,这样所有操作都能保持O(1)时间复杂度。

🚀 优雅的解决方案#

📝 代码详解#

让我们深入理解这个设计的精妙之处:

1. 双栈设计#

我们使用两个栈:主栈存储所有元素,辅助栈同步维护最小值状态。这是实现O(1)时间复杂度的关键。

2. Push操作的智慧#

  • 主栈:无条件压入新元素
  • 辅助栈:只在新元素小于等于当前最小值时压入 这样确保辅助栈顶始终是当前状态下的最小值

3. Pop操作的巧妙#

当主栈弹出的元素等于当前最小值时,辅助栈也要相应弹出,保持同步。

4. 获取最小值#

直接返回辅助栈顶元素,时间复杂度O(1)。

🎯 易错点提醒#

  1. 数值比较

    • 使用equals而不是==比较Integer对象
    • 考虑空栈情况的处理
  2. 辅助栈处理

    • push时要用<=而不是
    • pop时需要先比较再操作
  3. 边界条件

    • 栈为空时的处理
    • 重复元素的处理

💡 举一反三#

这种设计思想可以扩展到许多类似场景:

  1. 最大栈

    • 类似实现,但维护最大值
    • 适用于需要追踪最大值的场景
  2. 范围最值

    • 维护滑动窗口的最值
    • 股票价格跟踪系统
  3. 频率栈

    • 扩展实现最频繁元素的快速访问
    • 设计新闻热点排行系统

🎨 图解演示#

🌟 面试技巧#

  1. 设计思路表达

    • 先说明为什么需要辅助栈
    • 解释如何保证O(1)时间复杂度
  2. 优化讨论

    • 可以讨论空间优化的可能性
    • 提到处理特殊情况的考虑
  3. 扩展思考

    • 讨论如何扩展支持其他操作
    • 考虑并发场景的实现

🎩 空间优化版本#

如果对空间要求严格,我们可以在辅助栈中只存储差值:

这个优化版本通过存储差值而不是实际值,在某些情况下可以节省空间,但需要注意处理整数溢出的问题。


这道题展示了如何通过巧妙的数据结构设计来实现高效的功能。理解这种设计思想对于解决其他数据结构设计问题也很有帮助。如果你对这个设计还有任何疑问,欢迎在评论区讨论!