Java实现最小栈

眉间皱痕 提交于 2020-01-18 01:27:43

一、前言

  最近依旧在刷《剑指offer》的题目,然后今天写到了一道蛮有意思的题目,叫做包含min函数的栈,解题思路有点妙,写篇博客记录一下。


二、描述

  这道题目的描述是:定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))

  然后这题给出的原始代码如下,具体方法代码需要自己补充:

import java.util.Stack;

public class Solution {

    public void push(int node) {
        
    }
    
    public void pop() {

    }

    public int top() {

    }

    public int min() {

    }
}


三、思路

  看到这题,大多数人的第一反应应该就是:在类中声明一个变量minVal,记录当前栈中的最小值,然后在调用min方法时将这个最小值返回。但是仔细想一想,会发现这种办法是不可行的,因为如果执行了pop操作,将最小值出栈了,那我们怎么知道,剩下的元素中最小的是哪个,如何找到它从而去更新变量minVal呢?或许你认为可以在最小值出栈后,遍历剩下的元素,重新找出新的最小值。这样确实可以,相比于每次调用min都遍历一遍找最小值这种最笨的方法要好一些,但是别忘了,题目要求我们这个算法的时间复杂度是O(1),而且在面试中,这种方法是拿不到分的。所以,我们需要找出更加高效的算法。

  提高时间效率的一个常用方法就是牺牲空间换取时间,这里也可以使用这种办法。我们可以定义一个辅助栈minStack,帮助我们记录最小值。在我们的类中,需要有两个栈,一个就是我们用来存值的栈dataStack,另外一个就是帮助我们维护最小值的栈minStack

  push入栈操作有以下两种情况:

  • dataStack为空:此时,栈中没有元素,我们将push传入的参数直接放入到dataStack以及minStack中;
  • dataStack不为空:此时,将push操作传入的参数先放入dataStack中,然后判断这个元素与minStack的栈顶元素谁更大,若这个参数小于或等于minStack的栈顶元素,我们就将它加入到minStack中,否则minStack不变;

  这样,我们就可以保证,minStack的栈顶元素,一定是当前栈中最小的元素,而当我们调用min方法时,直接返回minStack的栈顶元素就行了。

  与push操作相对应的,pop出栈操作,也有两种情况:

  • 出栈的元素大于栈中最小值:此时dataStack的栈顶元素出栈,而minStack不变;
  • 出栈的元素等于栈中最小值:此时dataStack的栈顶元素出栈,同时,minStack的栈顶元素也出栈;

  这样做有什么意义呢?很简单,这个minStack里面的元素是怎么来的?如果当前入栈的元素小于等于最小值(即minStack的栈顶元素),我们就把他加入到minStack中,这样一路下来,minStack中的元素一定是单调递减的,而且栈顶元素一定是dataStack中的最小值,而栈顶元素的下一个元素,一定是所有元素中的第二小值,再往下就是第三小值,第四小值......依次类推(这里好好理解一下)。所以,如果我们现在出栈的值,和minStack的栈顶元素(即最小值)相等,那我们就让minStack的栈顶元素出栈,然后神奇的事情发生了:原来的第二小值现在变成了minStack的栈顶元素,而原来的最小值出栈后,第二小值就是新的最小值了;通过这种方式,我们就成功的解决了之前说过的,最小值出栈后,无法立即找到新最小值的问题;


栈中值重复引发的问题

  这里还有一个问题,就是:为什么也在进行push时,小于等于最小值的元素需要放入minStack中,而不是小于?这是因为,栈中可能存在重复的值,而最小值也可能有重复。比如说,【1,2,3,1】依次入栈,而第一次入栈的1就是最小值,第四个数也同样为1,它处在栈顶。假设我们使用的是小于,而不是小于等于,则四个数入栈后,minStack的值为【1】。此时,若栈顶元素1出栈,我们检测到出栈的数和最小值1相等,于是我们就让minStack的栈顶元素出栈,然后minStack就为空了,而dataStack是【1,2,3】。可是我们看的出来,栈中的最小值应该还是1,因为1在栈中出现了两次。此时就产生了问题,我们现在已经找不到最小值了。

  所以,为了防止这种情况发生,我们在push操作时,检测到当前入栈的元素小于等于最小值时,就需要将它加入minStack中,这时我们再看上面的例子:当四个数都入栈后,minStack的情况是【1,1】,dataStack为【1,2,3,1】,而此时,dataStack的栈顶元素1出栈,minStack的栈顶也出栈,则dataStack变成【1,2,3】,而minStack变成【1】,minStack的栈顶依旧是dataStack中的最小元素1,巧妙避免了上面的问题。


四、代码

  下面就是上述思路的实现,偷了点懒,下面的代码我直接使用了Java自带的栈来实现,主要是关注最小栈的实现思路,push或者pop这些操作的具体代码我就不自己写了;

import java.util.Stack;

public class Solution {

    private Stack<Integer> dataStack = new Stack<>();
    private Stack<Integer> minStack = new Stack<>();

    /**
    * 入栈操作
    */
    public void push(int node) {
        // 判断是否需要更新minStack
        if(dataStack.isEmpty() || minStack.peek() >= node) {
            minStack.push(node);
        }
        // 将元素放入dataStack
        dataStack.push(node);
    }

    /**
    * 出栈操作
    */
    public void pop() {
        // 若栈不为空才执行出栈
        if(!dataStack.isEmpty()) {
            // 若当前出栈的元素,等于栈中的最小值(即minStack的栈顶)
            // 则minStack的栈顶出栈
            if(dataStack.pop() == minStack.peek()) {
                minStack.pop();
            }
        }
    }

    /**
    * 查看栈顶元素
    */
    public int top() {
        return dataStack.peek();
    }

    /**
    * 返回栈中最小值
    */
    public int min() {
        // 返回最小值,即minStack的栈顶元素
        return minStack.peek();
    }
}
标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!