设计包含min函数的栈

题目:定义栈的数据结构,要求添加一个min函数,能够得到栈的最小元素。要求函数minpush以及pop的时间复杂度都是O(1)

分析:这是去年google的一道面试题。

我看到这道题目时,第一反应就是每次push一个新元素时,将栈里所有逆序元素排序。这样栈顶元素将是最小元素。但由于不能保证最后push进栈的元素最先出栈,这种思路设计的数据结构已经不是一个栈了。

在栈里添加一个成员变量存放最小元素(或最小元素的位置)。每次push一个新元素进栈的时候,如果该元素比当前的最小元素还要小,则更新最小元素。

乍一看这样思路挺好的。但仔细一想,该思路存在一个重要的问题:如果当前最小元素被pop出去,如何才能得到下一个最小元素?

因此仅仅只添加一个成员变量存放最小元素(或最小元素的位置)是不够的。我们需要一个辅助栈。每次push一个新元素的时候,同时将最小元素(或最小元素的位置。考虑到栈元素的类型可能是复杂的数据结构,用最小元素的位置将能减少空间消耗)push到辅助栈中;每次pop一个元素出栈的时候,同时pop辅助栈。

参考代码:

 

 1 #include <deque>
 2 #include <assert.h>
 3 
 4 template <typename T> class CStackWithMin
 5 {
 6 public:
 7       CStackWithMin(void) {}
 8       virtual ~CStackWithMin(void) {}
 9 
10       T& top(void);
11       const T& top(void) const;
12 
13       void push(const T& value);
14       void pop(void);
15 
16       const T& min(void) const;
17 
18 private:
19      deque<T> m_data;               // the elements of stack
20      deque<size_t> m_minIndex;      // the indices of minimum elements
21 };
22 
23 // get the last element of mutable stack
24 template <typename T> T& CStackWithMin<T>::top()
25 {
26       return m_data.back();
27 }
28 
29 // get the last element of non-mutable stack
30 template <typename T> const T& CStackWithMin<T>::top() const
31 {
32       return m_data.back();
33 }
34 
35 // insert an elment at the end of stack
36 template <typename T> void CStackWithMin<T>::push(const T& value)
37 {
38       // append the data into the end of m_data
39       m_data.push_back(value);
40 
41       // set the index of minimum elment in m_data at the end of m_minIndex
42       if(m_minIndex.size() == 0)
43             m_minIndex.push_back(0);
44       else
45       {
46             if(value < m_data[m_minIndex.back()])
47                   m_minIndex.push_back(m_data.size() - 1);
48             else
49                   m_minIndex.push_back(m_minIndex.back());
50       }
51 }
52 
53 // erease the element at the end of stack
54 template <typename T> void CStackWithMin<T>::pop()
55 {
56       // pop m_data
57       m_data.pop_back();
58 
59       // pop m_minIndex
60       m_minIndex.pop_back();
61 }
62 
63 // get the minimum element of stack
64 template <typename T> const T& CStackWithMin<T>::min() const
65 {
66       assert(m_data.size() > 0);
67       assert(m_minIndex.size() > 0);
68 
69       return m_data[m_minIndex.back()];
70 }

 

举个例子演示上述代码的运行过程:

  步骤              数据栈            辅助栈                最小值
1.push 3    3          0             3
2.push 4    3,4        0,0           3
3.push 2    3,4,2      0,0,2         2
4.push 1    3,4,2,1    0,0,2,3       1
5.pop       3,4,2      0,0,2         2
6.pop       3,4        0,0           3
7.push 0    3,4,0      0,0,2         0

讨论:如果思路正确,编写上述代码不是一件很难的事情。但如果能注意一些细节无疑能在面试中加分。比如我在上面的代码中做了如下的工作:

·         用模板类实现。如果别人的元素类型只是int类型,模板将能给面试官带来好印象;

·         两个版本的top函数。在很多类中,都需要提供const和非const版本的成员访问函数;

·         min函数中assert。把代码写的尽量安全是每个软件公司对程序员的要求;

·         添加一些注释。注释既能提高代码的可读性,又能增加代码量,何乐而不为?

总之,在面试时如果时间允许,尽量把代码写的漂亮一些。说不定代码中的几个小亮点就能让自己轻松拿到心仪的Offer。

以上转自何海涛博客

 

后面回帖中,有人提出了一个优化算法,可以减小维护最小值的辅助空间。

 

除了题目要求的栈之外新开一个栈,用来记录最小值,每当在原栈中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倍左右,虽然时间复杂度都是一样的.

 

以上转自anchor89

 

posted @ 2012-07-08 21:19  wolenski  阅读(293)  评论(0编辑  收藏  举报