昨天面试的题目,也是很经典的问题,当时由于提问的表述有问题,直接跳过了,回来以后找到这个题目,仔细看了一下,有一个改进算法很是精妙,具体如下:
原文链接:http://blog.youkuaiyun.com/anchor89/article/details/6055412
题目:定义栈的数据结构,要求添加一个min函数,能够得到栈的最小元素。要求函数min、push以及pop的时间复杂度都是O(1)。
注:这是06年一道Google的面试题.
先来说个常规解和他的一个优化,常规解的时间复杂度符合要求,但需要线性的额外空间.
常规解(参考 http://zhedahht.blog.163.com/blog/static/25411174200712895228171/):
除了题目要求的栈之外新开一个栈,用来记录最小值,每当在原栈中push数据后,与最小值栈中的栈顶元素比较,如果新值较小,则在最小值栈中push新值;否则再次push栈顶元素.
pop的时候,只要将最小值栈也pop一下就行了.
这样,min函数只需要返回最小值栈的栈顶元素即可.
常规解空间上的一个优化:
一般说来,最小值不会每次都需要更新,因此最小值栈里面会有很多重复元素.因此一个简单的优化就是在新值只当<=原最小值时才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倍左右,虽然时间复杂度都是一样的.
于是自己用python实现了一下,觉得蛮有意思,只是总觉得自己写的代码不够pythonic,还是代码写的太少了。
1 #usr/bin/evn python 2 ## simulate stack with min, push, pop O(1) 3 4 class stack(object): 5 6 def __init__(self): 7 self.values = [] 8 self.min_value = None 9 10 def push(self, x): 11 if self.min_value == None: 12 self.min_value = x 13 self.values.append(x-self.min_value) 14 if x < self.min_value: 15 self.min_value = x 16 17 def pop(self): 18 if len(self.values) == 0: 19 print "stack is empty, pop nothing." 20 exit(0) 21 else: 22 top = self.values.pop() 23 if top >= 0: 24 return top+self.min_value 25 else: 26 self.min_value -= top 27 return self.min_value+top 28 29 def opt_push(self, x): 30 if self.min_value == None: 31 self.min_value = x 32 if x >= self.min_value: 33 self.values.append(x) 34 else: 35 self.values.append(x*2-self.min_value) 36 self.min_value = x 37 38 def opt_pop(self): 39 if len(self.values) == 0: 40 print "stack is empty, pop nothing." 41 exit(0) 42 else: 43 top = self.values.pop() 44 if top >= self.min_value: 45 return top 46 else: 47 temp = self.min_value 48 self.min_value = 2*self.min_value - top 49 return temp 50 51 if __name__ == '__main__': 52 test_stack = stack() 53 tlist = [2,3,-5,7,4,1,-3,6,-4] 54 print tlist 55 for item in tlist: 56 test_stack.opt_push(item) 57 print test_stack.values 58 for i in xrange(len(tlist)): 59 print "current min value in stack is ", test_stack.min_value 60 print "stack pop value", test_stack.opt_pop()