题目描述:
定义栈的数据结构,请在该类型中实现一个能够得到栈最小元素的min函数。
分析:
发现现在的题目都有点言简意赅但是韵味隽永的感觉,我本来以为这就是一道很平常的题目,但是仔细查了下,这是一道Google06年的面试题,那么也就是说,这道题目的考察点还是很全面很有价值的。
首先,要求函数min、push以及pop的时间复杂度都是O(1)。
我们考虑在原本要求的栈之外新开一个栈,用来记录最小值,当原栈push数据后,与最小值栈中的栈顶元素比较,如果新值比较小,则在最小值栈中push新值,否则继续push最小值栈的栈顶元素。这样,pop的时候,只要将最小值栈也pop一下就可以了。而min函数返回最小值栈的栈顶元素即可。
如上的常规解法,需要额外的线性空间。具体实现的代码如下:
代码:
import java.util.Stack;
public class Solution {
private Stack<Integer> stack = new Stack<>();
private Stack<Integer> minStack = new Stack<>();
public void push(int node) {
stack.push(node);
minStack.push(minStack.isEmpty()?node:Math.min(minStack.peek(),node));
}
public void pop() {
stack.pop();
minStack.pop();
}
public int top() {
return stack.peek();
}
public int min() {
return minStack.peek();
}
}
优化:
常规解空间上的一个优化:
一般说来,最小值不会每次都需要更新,因此最小值栈里面会有很多重复元素.因此一个简单的优化就是:在新值当且仅当<=原最小值时,才pushIntoMin,注意这个==的条件是不可少的,这是为了防止在pop的时候错误的pop最小值。pop时, 当待pop值==最小值时,popMinStack, 其他时候不对最小值栈进行pop。
下面说一种具有常数空间复杂度的方法:
在这个方法里,只需要额外开一个用于存放当前最小值的变量min即可.因此下面提到的push和pop操作都是对于题目中要求的栈来操作的,当然,这也是这个算法里唯一的栈.
设push的参数为v_push,pop的返回值为v_pop.
先说下整体思路:因为栈中所有元素的值都不会小于当其为栈顶元素时min函数的值,所以在栈中其实只需要保存某元素比相应最小值大出来的值就可以了.而对于最小值更新的位置,栈元素肯定为0,因此可以利用这个位置来保存更多的信息,在这里是更新后前两个最小值的差值,而这个值肯定是非正的.
根据上面的思路,push函数按照如下策略进行:
首先push (v_push-min),如果v_push < min,更新min为v_push.
相应的,pop函数按照如下策略进行(称栈顶元素为top):
如果top >= 0, v_pop = min+top, 如果top < 0, v_pop = min,然后更新min为min-top.
显然,对于min函数来说,只需要返回min空间的内容即可.
与张霄学长交流后,学长也讲了一个类似的方法:
push时候 如果 v_push >= min, v_push 直接入栈, 如果 v_push < min, 那么入栈的是 2 * v_push - min, 然后 min = v_push. 出栈时, 如果栈顶的top >= min 直接出,如果 top < min 则出现异常,将min作为pop的返回值,另外需要还原前一个最小值,方法是 min = 2 * min - top
其实这两种方法在思路上是完全一样的,只是学长提供的方法里,栈中所有元素都比我的方法里大v_push.不过,这种变化直接带来的好处是,在不更新最小值的情况下,压栈值和出栈值都不需要额外的计算,在高级语言层面上,一次加减法运算比单纯的赋值至少多了一次访存操作和一次alu的运算,这样估计来我的方法耗时会是学长方法的2倍左右,虽然时间复杂度都是一样的.