栈的最小值

301 阅读4分钟

「这是我参与2022首次更文挑战的第29天,活动详情查看:2022首次更文挑战

前言

笔者除了大学时期选修过《算法设计与分析》和《数据结构》还是浑浑噩噩度过的(当时觉得和编程没多大关系),其他时间对算法接触也比较少,但是随着开发时间变长对一些底层代码/处理机制有所接触越发觉得算法的重要性,所以决定开始系统的学习(主要是刷力扣上的题目)和整理,也希望还没开始学习的人尽早开始。

系列文章收录《算法》专栏中。

力扣题目链接

问题描述

请设计一个栈,除了常规栈支持的pop与push函数以外,还支持min函数,该函数返回栈元素中的最小值。执行push、pop和min操作的时间复杂度必须为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.

剖析

要实现四个方法:

  1. push:往栈中继续存放,比较简单。
  2. pop:弹出栈顶数据,也比较简单。
  3. top:返回栈顶数据,也比较简单。
  4. getMin:返回最小值,需要在存放和pop过程中进行动态调整,这样才符合O(1)。

前面三个方法,都可以用单链表实现,那么怎么在O(1)时间内返回最小值呢?我们可以维护一个当前最小值currentMin字段,可以发现每次在push的时候最小值可能会变化,我们是否能把每次存放时的把currentMin标记在节点中呢?那我们在pop的时候最小值可能也会发现变化,那么如果我们在存放的时候就在节点中标记了最小值,那么现在的最小值是不是就是pop后的尾节点值呢?所以:

  1. 维护一个字段currentMin用于表明当前最小值,可以默认为Integer.MAX_VALUE。
  2. 节点增加一个字段标记存放此节点时最小值(包括当前存放的节点关键字),如果是第一个节点push时最小值为节点关键字。
  3. pop的时候,获得要弹出节点的下面节点,拿到下面节点的标记的最小值赋值给当前最小值字段currentMin。

代码

package com.study.algorithm.stack;

/**
 * 栈的最小值
 * https://leetcode-cn.com/problems/min-stack-lcci/
 * 
 * 要实现四个方法:
 * 1. push:往栈中继续存放,比较简单。
 * 2. pop:弹出栈顶数据,也比较简单。
 * 3. top:返回栈顶数据,也比较简单。
 * 4. getMin:返回最小值,需要在存放和pop过程中进行动态调整,这样才符合O(1)。
 *
 * 前面三个方法,都可以用单链表实现,那么怎么在O(1)时间内返回最小值呢?我们可以维护一个当前最小值currentMin字段,可以发现每次在push的时候最小值可能会变化,我们是否能把每次存放时的把currentMin标记在节点中呢?那我们在pop的时候最小值可能也会发现变化,那么如果我们在存放的时候就在节点中标记了最小值,那么现在的最小值是不是就是pop后的尾节点值呢?所以:
 * 1. 维护一个字段currentMin用于表明当前最小值,可以默认为Integer.MAX_VALUE。
 * 2. 节点增加一个字段标记存放此节点时最小值(包括当前存放的节点关键字),如果是第一个节点push时最小值为节点关键字。
 * 3. pop的时候,获得要弹出节点的下面节点,拿到下面节点的标记的最小值赋值给当前最小值字段currentMin。
 */
public class MinStack {
    private int currentMin = Integer.MAX_VALUE;

    class Node {
        //节点关键字
        private Integer value;
        //存放节点时的最小值(包括当前存放的节点关键字)
        private int currentMin;
        // 前面一个节点
        private Node pre;

        public Node() {
        }

        public Node(Integer value) {
            this.value = value;
        }
    }

    //尾节点
    private Node tail = new Node();

    /**
     * initialize your data structure here.
     */
    public MinStack() {

    }

    /**
     * 存放第一个节点的时候该节点的currentMin就是自己的关键字
     * 存放其他节点的时候需要插入节点并拿到当前最小的值赋给当前插入的关键字的currentMin
     *
     * @param x
     */
    public void push(int x) {
        Node newNode = new Node(x);
        Node tailPre = tail.pre;
        if (tailPre == null) {
            tail.pre = newNode;
            newNode.currentMin = x;
            currentMin = x;
        } else {
            tail.pre = newNode;
            newNode.currentMin = Math.min(currentMin, x);
            currentMin = newNode.currentMin;
            newNode.pre = tailPre;
        }
    }

    /**
     * 从链表中移除最后一个节点即可
     */
    public void pop() {
        Node tailPre = tail.pre;
        if (tailPre == null) {
            return;
        } else {
            Node tailPrePre = tailPre.pre;
            tail.pre = tailPrePre;
            //当前最小值替换,当是已经没有节点就重新赋值为Integer.MAX_VALUE
            if (tailPrePre != null) {
                currentMin = tailPrePre.currentMin;
            } else {
                currentMin = Integer.MAX_VALUE;
            }
        }
    }

    /**
     * 直接返回尾部节点的currentMin
     *
     * @return
     */
    public int top() {
        Node tailPre = tail.pre;
        if (tailPre != null) {
            return tailPre.value;
        }
        return 0;
    }

    /**
     * 直接返回尾部节点的currentMin
     *
     * @return
     */
    public int getMin() {
        return currentMin;
    }

    public static void main(String[] args) {
        MinStack minStack = new MinStack();
        minStack.push(-2);
        minStack.push(0);
        minStack.push(-3);
        minStack.getMin();   //--> 返回 -3.
        minStack.pop();
        minStack.top();      //--> 返回 0.
        minStack.getMin();  // --> 返回 -2.
    }
}