LeetCode 155. 最小栈 - 思考与最优解

分析

题目链接

是一种先进后出的数据结构,对于栈来说,如果一个元素 a 在入栈时,栈里有其它的元素 b, c, d,那么无论这个栈在之后经历了什么操作,只要 a 在栈中,b, c, d 就一定在栈中。

因此,在对栈操作过程中的任意一个时刻,只要栈顶的元素是 a,那么我们就可以确定栈里面现在的元素一定是 a, b, c, d

那么,我们可以在每个元素 x 入栈时把当前栈(包括 {a, b,...,x})的最小值 min 存储起来。在这之后无论何时,如果栈顶元素是 x,我们就可以直接返回存储的最小值 min

现在我们需要考虑:如何实现 “存储 min 值” 这个操作。

首先可以想到,在对某个元素入栈时,把当前主栈的最小值 push 到另外一个栈中,也就是引入一个辅助栈。并且对于主栈的每一次 push 和 pop 操作,辅助栈也会进行相应的 push 和 pop 操作,保证任意时刻,辅助栈的栈顶元素都记录着当前主栈的最小值。

更进一步,由于主栈和辅助栈的元素是一一对应的,也就是两者必然同时出现,那么可以考虑弃用辅助栈,取而代之,对于主栈的每一次入栈操作,push 一个二元组 <a, min> 。其中 a 表示当前元素,min 表示当前栈的最小值。

以上两种方法的时间复杂度为 \(O(1)\), 空间复杂度为 \(O(n)\),其中空间复杂度有没有优化的空间呢?

有,但是对于题目的要求有一定限制。

具体来说,我们可以引入一个辅助常量 min,表示当前栈的最小值。那么栈中的每个元素不能是当前元素的原始值了,而是当前元素与 min差值。当然,我们在对栈进行 push,pop 操作时,也需要按照一定的策略更新这个 min 值,以确保它能正确表示当前栈的最小值。

这种方法的限制就在于,因为要存储当前元素与 min差值,考虑极端情况下,使用 int 类型来保存可能会发生溢出,虽然这时可以考虑使用 long 类型,不过 long 类型的存储空间大小是 int 类型的二倍,还是付出了空间的代价。但是对于数据范围在 $\left [ -2^{30} + 1 , \ \ 2^{30} - 1 \right ] $ 时,这种方法的时间和空间复杂度都为 \(O(1)\)

下面给出具体实现:

辅助栈

class MinStack {
    // 两个栈:主栈和辅助栈
    Stack<Integer> st1;
    Stack<Integer> st2;

    public MinStack() {
        st1 = new Stack<>();
        st2 = new Stack<>();
    }
    
    public void push(int val) {
        if (st1.empty()) {
            st1.push(val);
            st2.push(val);
        } else {
            st1.push(val);
            int min = st2.peek();
            min = min < val ? min : val;
            st2.push(min);  // 辅助栈只记录当前的最小值
        }
    }
    
    public void pop() {
        st1.pop();
        st2.pop();
    }
    
    public int top() {
        return st1.peek();
    }
    
    public int getMin() {
        return st2.peek();
    }
}

辅助栈 pro

class MinStack {
    Stack<Node> st;

    // 用一个 Node 同时存储 val 和当前的 min 值
    class Node {
        int val;
        int min;

        Node(int val, int min) {
            this.val = val;
            this.min = min;
        }
    }

    public MinStack() {
        st = new Stack<Node>();
    }
    
    public void push(int val) {
        if (st.empty()) {
            Node tmp = new Node(val, val);
            st.push(tmp);
        } else {
            Node pre = st.peek();
            int min = pre.min < val ? pre.min : val;
            Node tmp = new Node(val, min);
            st.push(tmp);
        }
    }
    
    public void pop() {
        st.pop();
    }
    
    public int top() {
        return st.peek().val;
    }
    
    public int getMin() {
        return st.peek().min;
    }
}

进阶:辅助常量

class MinStack {
    Stack<Integer> st;
    int min;

    public MinStack() {
        st = new Stack<>();
    }
    
    public void push(int val) {
        if (st.empty()) {
            min = val;
            st.push(0);
        } else {
            int diff = val - min;  // 可能会发生溢出
            if (diff < 0) {   // 差值小于零,意味着当前 val 为最小,需要更新 min 值
                min = val;
            } 
            st.push(diff);
        }
    }
    
    public void pop() {
        int tmp = st.pop();
        if (tmp < 0) {   // 如果弹出了最小值,就要记得更新 min 值
            min = min - tmp;
        }
    }
    
    public int top() {
        return st.peek() < 0 ? min : st.peek() + min;
    }
    
    public int getMin() {
        return min;
    }
}
posted @ 2023-05-24 22:46    阅读(132)  评论(0)    收藏  举报