最小栈——辅助栈&一个栈解法

717 阅读4分钟

前言

在开始最小栈的算法之前,我们先来了解一下java中是如何实现数据结构栈的。

image-20210217083316134

Stack继承自线程安全的数组Vector,既然是继承关系,那Stack也会拥有Vector声明为public的方法和变量。即Stack的基因是支持动态扩容的。但Stack出于更高层次的抽象,不提供这样的方法,而交由加强版的Deque队列实现,可见,栈不过是加了限制的队列。

image-20210217083859827

我们不妨顺着顶层注释顺藤摸瓜,看看ArrayDeque的概况。

image-20210217084310336

显然,它支持动态扩容,同时也提供了push,pop,peek,poll等操作,既然栈可以用数组实现,那链表也自然可以——

image-20210217084532035

我们发现LinkedList真是一个全能选手,它即是List,又是Queue,还是Deque。但是我们在使用的时候,总是用特定的接口来引用它,这是因为持有接口说明代码的抽象层次更高,而且接口本身定义的方法代表了特定的用途。

说这么多,核心的思想是想告诫大家,别在任何场景下都去写:Stack<Object> stack = new Stack<>()啦!!!


一. 最小栈

1.1 问题描述

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。

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

1.2 思路及复杂度分析

最小栈问题的核心在于常数时间内检索最小元素,也就是说我们需要一个内存空间实时的保存这个最小值。那这个内存空间用什么数据结构实现呢?

显然,声明一个最小值的全局变量是可以的,有着自动移动栈顶指针特性的同样可以,我们这里只介绍使用栈的思路,它的具体实现是这样的:

在压栈的时候,将要压栈的元素与最小值栈的元素比较,小于的话就更新最小栈。需要得到最小值时,只需要peek()弹出最小值栈的栈顶元素即可。看起来的确很easy的样子咯,如果你也这样认为的话,那就有继续往下看的必要——

假如有这样的情景,我们首先删除了数据栈的栈顶元素,这时最小值栈的栈顶元素也需要同步更新,但是我根本没有保存上个最小值,这可怎么办呢~

一种通俗的解决方式是在压入最小值栈时,不要更新原有的最小值,而是作为栈顶元素插入,这样固然可以解决问题,甚至性能也完全看得过去,但能不能只使用一个栈就能同时存储最小值和数据呢?

这就需要使用到Node啦。我们首先看下java源码中是如何应用Node的——

HashMap中的Node

同样,这里我们可以类比key,Value的形式将数据与最小值作为Node类的属性:

private static class Node{
    private int val;
    private int min;
    public Node(int val,int min){
      this.val = val;
      this.min = min;
    }
  }

使用一个栈的解法,因为每一个最小值都会被存储导致性能略低于使用辅助栈的方式,但其代码可读性高,还是一种很值得的尝试。


再来简要看下两种解法的复杂度分析:

  1. 辅助栈:时间复杂度O(1).空间复杂度O(N)
  2. 一个栈:时间复杂度O(1),空间复杂度O(N),但因为申请一个栈空间比申请多个Node节点对的空间要少,所以空间复杂度相对辅助栈较高。

1.3 趣味图解

辅助栈

图片来自力扣官方题解

一个栈

请移步力扣题解留言区:leetcode-cn.com/problems/mi…

image-20210217134958026

1.4 代码演示

/**
*最小栈的解法
*、
class MinStack {
    private Stack<Node> stack;

    /** initialize your data structure here. */
    public MinStack() {
        stack = new Stack<>();
    }
    
    public void push(int x) {
        if(stack.empty())
            stack.push(new Node(x, x));
        else
            stack.push(new Node(x, Math.min(x,stack.peek().min)));
    }
    
    public void pop() {
        stack.pop();
    }
    public int top(){
        return stack.peek().val;
    }
    
    public Integer getMin(){
        return stack.peek().min;
    }
    private static class Node{
        private int val;
        private int min;
        public Node(int val,int min){
            this.val = val;
            this.min = min;
        }
    }
}

/**
*辅助栈的解法
*/
class MinStack {
    //数据栈
    Stack<Integer> dataStack = null;
    //最小值栈
    Stack<Integer> minStack = null;
    /** initialize your data structure here. */
    public MinStack() {
        dataStack = new Stack<Integer>();
        minStack = new Stack<Integer>();
    }
    
    public void push(int x) {
        dataStack.push(x);
        if(minStack.isEmpty() || x<=minStack.peek())
            minStack.push(x);
    }
    
    public void pop() {
        if(dataStack.pop().equals(minStack.peek())){
            minStack.pop();
        }        
    }
    
    public int top() {
        return dataStack.peek();
    }
    
    public Integer getMin(){
        if(!minStack.isEmpty())
            return minStack.peek();
        //throw new EmptyStackException();
        return null;
    }
}


日拱一卒,功不唐捐。