包含min函数的栈
题目描述
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的min函数。在该栈中,调用min, push, pop的时间复杂度都是 O ( 1 ) O(1) O(1)
分析
由于实现的是一个栈的数据结构,要求数据先进后出,如果我们对push进来的元素排序,显然就破坏了这一顺序;如果每次求最小值都遍历栈中所有元素,则min函数时间复杂度会为
O
(
n
)
O(n)
O(n).
这一问题我们可以考虑采用额外空间换取时间的策略。在我们定义的栈的类中,包含两个std::stack类型的成员,一个用于记录原本的数据的data,和用于记录最小值的minD。data用于记录数据并保持其先进后出顺序,mindD则用于保存数据的最小值,也即minD的栈顶元素是当前最小值。
在push时,数据t直接压入data中。当minD为空(这是第一个入栈元素)或当前数据小于当前栈顶元素,说明该数据入栈后,此时栈的最小值更新为t,因此要将t放在minD的栈顶,所以将数据t压入minD:反之,说明t不是当前最小的元素,它的加入不会改变栈的最小值,所以压入t后最小值不变,仍是当前的栈顶元素值,因此将minD.top()再一次压入minD中。
pop时,首先确认当前栈不为空,然后将data和minD的栈顶元素分别弹出即可。
getMin函数,返回minD的栈顶元素即可,因为栈顶元素记录的是当前(栈中拥有这些元素时)的最小值。
举例说明:
操作 | data | minD | 最小值 |
---|---|---|---|
压入3 | 3 | 3 | 3 |
压入2 | 3,2 | 3,2 | 2 |
压入1 | 3,2,1 | 3,2,1 | 1 |
压入3 | 3,2,1,3 | 3,2,1,1 | 1 |
弹出3 | 3,2,1 | 3,2,1 | 1 |
弹出1 | 3,2 | 3,2 | 2 |
压入过程中,data压入3,栈为空,因此minD也压入3,因为此时最小元素就是3;
压入2,由于此时2比minD的top即3要小,意味着压入2使得最小值变化,minD压入2;压入1时同理
压入第二个3时,由于minD栈顶是1,意味着此时最小值是1,3的加入不改变最小值,因此minD仍然压入1,代表此时最小值仍为1.弹出时弹出两个栈的栈顶即可。
C++实现如下:
注意:这里还有一个问题,涉及到pop()的实现以及C++表达式计算的顺序问题。
我的实现中pop()方法功能是弹出并返回这个弹出的栈顶元素,在stl中栈的pop()函数只弹出不返回数据,top()函数只返回栈顶元素不操作,这两部分是分开的。事实上对于C++来说分开的应该更科学一点,如果pop本身即出栈又返回的话,如果执行cout << s.pop() <<s.pop() << endl;
这两个的pop()并没有严格的执行顺序(在C++中,例如计算f()+g()时,两个函数的计算顺序是没有严格规定的)导致上述语句的输出可能是未定义的,尽管出栈的顺序是正确的,但不能保证cout的顺序也是正确的。我目前掌握的资料是,C++双目运算符(除了逻辑运算符)左右两边优先级一致时是不严格指定计算顺序的,比如 a()+b()*c()+d()
,肯定是先算乘后算加,但是这四个表达式未必就按照这个顺序来计算。回到上述的这个问题,如果一个栈先后压入1,2,弹出顺序就是2,1;但按照上述方式出栈输出,有可能后边的pop先计算得到2,前一个后计算返回1,这样的输出反而是1,2。因此大家最好将pop()和top()分开实现,或者不要使用连续cout同一个变量的表达式返回值,以免输出有误。关于这一点,也欢迎大家一起讨论。
template <typename T>
class stackWithMin {
public:
void push(const T & t);
T pop();
T getMin();
private:
stack<T> data;
stack<T> minD;
};
template <typename T>
void stackWithMin<T>::push(const T & t) {
data.push(t);
if (minD.empty() || t < minD.top())
minD.push(t);
else
minD.push(minD.top());
}
template <typename T>
T stackWithMin<T>::pop() {
assert(!data.empty() && !minD.empty());
T t = data.top();
data.pop();
minD.pop();
return t;
}
template <typename T>
T stackWithMin<T>::getMin() {
assert(!data.empty() && !minD.empty());
return minD.top();
}