155. 最小栈
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
push(x) —— 将元素 x 推入栈中。
pop() —— 删除栈顶的元素。
top() —— 获取栈顶元素。
getMin() —— 检索栈中的最小元素。
示例:
输入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[],[-2],[0],[-3],[],[],[],[]]
输出:
[null,null,null,null,-3,null,0,-2]
解释:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); --> 返回 -3.
minStack.pop();
minStack.top(); --> 返回 0.
minStack.getMin(); --> 返回 -2.
提示:
pop、top 和 getMin 操作总是在 非空栈 上调用。
疑问
当时我看到这一道题目的时候,首先想到的是用一个min值保存当前最小的值,但是转念一想,如果min就是栈顶元素,那么一旦pop()之后,min还是最小值吗?题目要求的要在 O ( 1 ) O(1) O(1)的时间内获取栈内元素最小值,那么只用一个min肯定就不行了,因为min只能保存一次最小值,一旦pop之后,min就未必是栈内剩余元素的最小值了。这里就为后面要学的内容 单调栈 埋下了伏笔。
辅助栈
辅助栈,顾名思义,就是辅助的栈,这个栈的功能就是保存当前栈内最小元素的值。
辅助栈的思路呢,就是说创建一个新的栈,姑且叫他最小栈吧,每当数据栈插入一个新元素的时候,就与最小栈的栈顶元素进行比较,如果新插入的数据小于等于最小栈的栈顶元素,那么就把这个数据插入到最小栈中。
为什么要这么做呢?
首先我们来看题目,要求输出最小值,那么我创建一个min_Stack
用来保存当前数据栈中的最小值,首先第一个元素插入进来,那么它一定是当前的最小值(毕竟就一个元素嘛),所以插入到min_Stack
中。然后插入第二个元素,如果这个新元素大于min_Stack
中的栈顶元素,那么他肯定不是当前最小的元素,不插入了,没啥好说的。如果小于等于min_Stack
的栈顶元素的话,那么说明新来的就是当前栈中的最小元素,所以插入到最小栈中,这样就可以保证min_Stack
中的元素一定是从栈底到栈顶降序(不太准确,应该是非升序)(因为新插入到最小栈的元素一定比栈顶元素小(或者等于))。然后当数据栈出栈的时候,判断数据栈栈顶元素和最小栈栈顶元素是否相等,如果相等,那么就同时pop
,否则只有数据栈pop
。
为什么判断的时候是 小于等于 呢?
比如现在有一个序列5,4,3,3,3,3,5,4
,现在我把5,4,3
插入到最小栈中了,下一个新数据 x 还是3,假设我不插入的话,那么最终的最小栈数据为5,4,3
,然而这个程序的逻辑就是如果最小栈元素和数据栈栈顶元素相等时,两个栈都pop
,假如我现在把数据栈中的 3 给pop
了,因为最小栈的栈顶也是 3 ,所以也跟着pop
,但是数据栈的栈顶元素还是 3 ,而最小栈中的数据是5,4
,也就是说如果数据栈中插入的元素是相同的时候,也需要插入到最小栈中。
下面是程序的代码:
class MinStack {
public:
/** initialize your data structure here. */
stack<int>data_Stack;
stack<int>min_Stack;
MinStack() {
}
void push(int x) {
//如果最小栈为空或者最小栈栈顶元素 大于等于 x,那么就push
if(min_Stack.empty() || min_Stack.top() >= x)min_Stack.push(x);
data_Stack.push(x);
}
void pop() {
if(min_Stack.top() == data_Stack.top())min_Stack.pop();
data_Stack.pop();
}
int top() {
return data_Stack.top();
}
int getMin() {
return min_Stack.top();
}
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack* obj = new MinStack();
* obj->push(x);
* obj->pop();
* int param_3 = obj->top();
* int param_4 = obj->getMin();
*/
如何理解最小栈呢?
其实我感觉这个最小栈有点类似于“养备胎”,我想要找到最爱我的女生。有一个“女人栈”和一个“女友栈”,“女人栈”是苍天让我遇到的所有女生,我的所有女友都要在这里找,“女友栈”的栈顶元素是我的现女友,现女友以下皆为“备胎”。现在来了一个女生,她是第一个,只能是她,所以我就把这个女生先push
进“现女友栈”,直到苍天又给我的“女人栈”中push
了另外一个更优秀的女生,她更爱我,所以我就把她push
到“现女友栈”,作为栈顶女友,栈顶女友下面的都是备胎女友,直到。。。。直到我遇到了新push
进来的女生,假如她没有我的现女友更爱我,那我的现女友栈的栈顶不变(没有我的现女友爱我,我为什么要让她当新女友),所以如果后push
进来的女生如果没有现女友更爱我,她们爱咋咋地,我的现女友不会变,但是直到我的“女人栈”里的栈顶元素就是我的现女友了,她不爱我了,她要走,那我只能忍痛割爱,把我的“现女友栈”的栈顶元素给pop
了,正式启用“备胎1号”,直到,,,直到一个备胎都没有了,并且我的“女人栈”里也一个也没有了。唉,早知道就不养备胎了,进来一个我就直接return
了。
新节点法
创建一个节点,保存当前的数据值和最小值
这个其实很好理解,所以我不想多说了,直接看代码吧。
class MinStack {
public:
/** initialize your data structure here. */
struct Node
{
int val;
int min;
Node(int v,int m):val(v),min(m){
}; //相当于构造函数吧
};
stack<Node>sta;
MinStack() {
}
void push(int x) {
//把新元素与栈顶元素的min进行比较
if(sta.empty())sta.push(Node(x,x));
else sta.push(Node(x,min(x,sta.top().min)));
}
void pop() {
sta.pop();
}
int top() {
return sta.top().val;
}
int getMin() {
return sta.top().min;
}
};
/**
* Your MinStack object will be instantiated and called as such:
* MinStack* obj = new MinStack();
* obj->push(x);
* obj->pop();
* int param_3 = obj->top();
* int param_4 = obj->getMin();
*/
这个方法真的太好了,比上面的简洁,我感觉效率还挺高。
就先写上面这两种方法吧,感觉最小栈的方法挺不错的,大家还有相似的题目吗?