一、前言
最近依旧在刷《剑指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(); } }
来源:https://www.cnblogs.com/tuyang1129/p/12208041.html