队列和栈——最小栈的实现

01最小栈实现原理

最小栈是一种特殊的栈结构,除了支持标准栈的所有操作外,还需要在O(1)时间内找到栈中的最小元素。

这是一个经典的数据结构设计问题,不通过遍历实现时间复杂度为O(1),考虑可以用空间换时间的思想。

问题分析

 问题描述 LeetCode 155: https://leetcode.cn/problems/min-stack/ 

设计一个支持以下操作的栈:

- push(x) - 将元素x推入栈中

- pop() - 删除栈顶元素

- top() - 获取栈顶元素

- getMin() - 检索栈中的最小元素

所有操作的时间复杂度都必须是O(1)。

核心思路

使用辅助栈记录最小值:

- data栈:存储实际数据

- min栈:存储到当前位置的最小值

关键点:min栈data栈保持同步,每次push操作,min栈都要压入一个值(当前最小值)。

02基于Java内置Stack

方案一:基于Java内置Stack

实现代码

    import java.util.Stack;/** * 基于Java内置Stack的最小栈实现 * 缺点是常数时间慢,有同步开销 */class MinStackWithBuiltIn {    private Stack<Integer> dataStack;   // 存储实际数据    private Stack<Integer> minStack;    // 存储最小值信息        public MinStackWithBuiltIn() {        dataStack = new Stack<>();        minStack = new Stack<>();    }        /**     * 压栈操作     * @param val 要压入的值     */    public void push(int val) {        dataStack.push(val);                // 更新最小值的栈:如果新值更小或相等,或者是第一个元素,则压入栈内        if (minStack.isEmpty() || val <= minStack.peek()) {            minStack.push(val);        } else {            // 保持与dataStack同步数量,所以重复压入当前最小值            minStack.push(minStack.peek());        }    }        /**     * 弹栈操作     */    public void pop() {        dataStack.pop();        minStack.pop();  // 两个栈保持同步    }        /**     * 获取栈顶元素     */    public int top() {        return dataStack.peek();    }        /**     * 获取当前栈中最小元素     */    public int getMin() {        return minStack.peek();    }
    

    时间复杂度:

    - push():O(1) - 栈的基本操作

    - pop():O(1) - 栈的基本操作  

    - top():O(1) - 直接访问栈顶

    - getMin():O(1) - 直接访问最小值栈顶

    缺点是常数时间慢,即便是O(1)常数时间

    空间复杂度:

    - O(n) - 需要两个栈,每个栈最多存n个元素

    03基于数组实现的优化

    实现代码

      /** * 基于数组的最小栈实现 * 优点:常数时间快,无同步开销 * 缺点:需要预估容量上限 */class MinStackWithArray {    private static final int MAXN = 8001;  // 根据面试题题干,确定的最大容量    private int[] dataArray;    // 存实际数据的数组    private int[] minArray;     // 存最小值的数组    private int size;           // 当前栈的大小        public MinStackWithArray() {        dataArray = new int[MAXN];        minArray = new int[MAXN];        size = 0;    }        /**     * 压栈操作     */    public void push(int val) {        dataArray[size] = val;                // 更新最小值数组        if (size == 0 || val <= minArray[size - 1]) {            minArray[size] = val;       // 新的最小值        } else {            minArray[size] = minArray[size - 1];  // 沿用之前的最小值        }        size++;    }        /**     * 弹栈操作     */    public void pop() {        size--;  // 只需要减少size即可,无需清理数据!    }        /**     * 获取栈顶元素     */    public int top() {        return dataArray[size - 1];    }        /**     * 获取当前栈中最小元素     */    public int getMin() {        return minArray[size - 1];    }}
      

      时间复杂度:

      - push():O(1) - 数组赋值操作

      - pop():O(1) - 只需修改size

      - top():O(1) - 直接数组访问

      - getMin():O(1) - 直接数组访问

      时间复杂度为O(1),常数操作时间很快。

      空间复杂度:

      - O(MAXN) - 预分配固定大小的数组空间

      04算法细节分析

      最小值栈的同步策略

      // 压栈时的最小值更新逻辑

      if (val <= currentMin) {

          minStack.push(val);        // 新的最小值

      } else {

          minStack.push(currentMin); // 保持同步,重复压入当前最小值

      }

      这种方式的原则是:

      - 两个栈始终保持相同的大小

      - 弹栈时无需额外判断,两栈直接同步弹出

      - 任何时候最小栈顶都是当前的最小值

      边界条件处理

      // 第一个元素的特殊处理

      if (minStack.isEmpty() || val <= minStack.peek()) {

          minStack.push(val);

      }

      性能优化要点

      常数时间优化

      - 数组访问 vs 栈操作:数组直接索引比栈的方法调用更快

      - 避免同步开销:Java的Stack继承自Vector,有同步机制

      - 减少对象创建:数组实现避免了栈节点对象的频繁创建

      空间优化策略

      // 空间优化版本:只在必要时压入最小值

      if (val <= currentMin) {

          minStack.push(currentMin);  // 先压入旧的最小值

          minStack.push(val);         // 再压入新的最小值

      }

      适用场景分析

      选择Stack实现的场景

      - 快速原型开发

      - 对性能要求不严格

      - 栈大小变化范围大且不可预估

      选择数组实现的场景

      - 对性能要求严格的场合

      - 可以预估栈大小上限的情况

      Anyway,最小栈的核心思想是**空间换时间**,通过额外的数据结构,来实现O(1)的查询效率。

      文章参考:bilibili左程云算法系列

      青轴作响,叨叨有声。

      欢迎留言,一起分享你的技术见闻。每一篇都聊技术人的真实选择与成长。IT之路,不孤单。

      声明:文章仅代表作者本人观点,供学习和交流使用。

      公众号:叨叨猿的青轴日记

      更多往期内容请在公众号回复[算法那些事]

      合作/投稿:daodaoyuanblue@163.com

      评论
      添加红包

      请填写红包祝福语或标题

      红包个数最小为10个

      红包金额最低5元

      当前余额3.43前往充值 >
      需支付:10.00
      成就一亿技术人!
      领取后你会自动成为博主和红包主的粉丝 规则
      hope_wisdom
      发出的红包
      实付
      使用余额支付
      点击重新获取
      扫码支付
      钱包余额 0

      抵扣说明:

      1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
      2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

      余额充值