最小栈

题目描述

来源于 https://leetcode-cn.com/

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

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

示例:

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

解法:

首先请假想一组数字,并在纸上模拟入栈的过程。在此过程中,能够很轻易地发现,只需要记录下先前入栈的数中的最小值,用此最小值和当前待入栈的数比较,就可以得到当前数入栈后栈中的最小值。

但是如果入栈和出栈穿插进行,如果最小值被出栈了,那么栈中余下的数中最小值就不得而知了。但是,可以在每次入栈时把当前值和最小值作为一个元组入栈,即每次入栈的是 (value, min)。栈顶的元素的 min 就是当前栈中的最小值。有新的数字入栈时,和栈顶元素的中 min 比较,计算出最小值即可。这样,有元素出栈后,栈顶元素中的 min 依然是栈中的最小值。

class MinStack {
public:
    void push(int val) {
        int min_val = val;
        if(!stack.empty()){
            min_val = ::min(val, stack.top()[1]);
        }
        stack.push({val, min_val});
    }

    void pop() {
        stack.pop();
    }

    int top() {
        return stack.top()[0];
    }

    int min() {
        return stack.top()[1];
    }

private:
    stack<array<int, 2>> stack;
};

上面这个方法,时间复杂度上完全达标,空间是常规的栈的二倍。下面考虑如何在空间复杂度上优化。

如果入栈的数比最小值要大,那么最小值是不变的。如果入栈的数小于最小值,那么最小值需要被更新。在出栈的时候,如果当前值等于最小值,则需要把最小值更新为前一个最小值。基于以上考虑,需要记录下历史上的最小值,这是一个递减的序列,且需要在序列后面加入或者删除值,这个序列的操作符合栈的特性。

基于以上分析,只需要使用另外一个栈,叫做 stack_min,用来保存历史上产生过的最小值。stack_min 的栈顶保存的是目前最小栈中的最小值,基于该最小值与新插入的值进行比较,就可以决定是否插入新的最小值。出栈的时候,如果出栈元素等于 stack_min 的栈顶元素,那就出栈。

class MinStack {
public:
    void push(int val) {
        stack_val.push(val);

        if(stack_min.empty() || stack_min.top() >= val){
            stack_min.push(val);
        }
    }

    void pop() {
        if(stack_val.top() == stack_min.top()){
            stack_min.pop();
        }
        stack_val.pop();
    }
    
    int top() {
        return stack_val.top();
    }

    int min() {
        return stack_min.top();
    }

private:
    stack<int> stack_val;
    stack<int> stack_min;
};