最小栈 题解
一、题目描述
设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
实现 MinStack
类:
-
MinStack()
初始化堆栈对象。 -
void push(int val)
将元素val
推入堆栈。 -
void pop()
删除堆栈顶部的元素。 -
int top()
获取堆栈顶部的元素。 -
int getMin()
获取堆栈中的最小元素。
二、解题思路
为了在常数时间内检索到最小元素,我们可以使用两个栈:一个主栈 _st
用于存储所有元素,另一个辅助栈 _minst
用于存储当前的最小元素。
-
push(int val)
操作:-
将元素
val
压入主栈_st
。 -
检查辅助栈
_minst
是否为空,或者val
是否小于等于辅助栈_minst
的栈顶元素。如果是,则将val
也压入辅助栈_minst
。这样可以保证辅助栈_minst
的栈顶元素始终是当前主栈_st
中的最小元素。
-
-
pop()
操作:-
检查主栈
_st
的栈顶元素是否等于辅助栈_minst
的栈顶元素。如果相等,说明即将从主栈_st
中弹出的元素是当前的最小元素,因此需要将辅助栈_minst
的栈顶元素也弹出。 -
弹出主栈
_st
的栈顶元素。
-
-
top()
操作:-
直接返回主栈
_st
的栈顶元素。
-
-
getMin()
操作:-
直接返回辅助栈
_minst
的栈顶元素,因为辅助栈_minst
的栈顶元素始终是当前主栈_st
中的最小元素
-
三、代码
#include <stack>class MinStack {
public:// 主栈,存储所有元素std::stack<int> _st;// 辅助栈,存储当前的最小元素std::stack<int> _minst;MinStack() {}void push(int val) {_st.push(val);if (_minst.empty() || val <= _minst.top()) {_minst.push(val);}}void pop() {if (_st.top() == _minst.top()) {_minst.pop();}_st.pop();}int top() {return _st.top();}int getMin() {return _minst.top();}
};
四、复杂度分析
-
时间复杂度:
push(int val)
操作:每次压入元素时,主栈和辅助栈的操作时间复杂度均为 \(O(1)\),所以总的时间复杂度为 \(O(1)\)。pop()
操作:每次弹出元素时,主栈和辅助栈的操作时间复杂度均为 \(O(1)\),所以总的时间复杂度为 \(O(1)\)。top()
操作:获取主栈栈顶元素的时间复杂度为 \(O(1)\)。getMin()
操作:获取辅助栈栈顶元素的时间复杂度为 \(O(1)\)。
-
空间复杂度:
- 主栈
_st
存储所有元素,在最坏情况下,需要存储 n 个元素,空复杂度为 \(O(n)\)。 - 辅助栈
_minst
在最坏情况下,也需要存储 n 个元素(例如元素依次递减入栈),空间复杂度为 \(O(n)\)。所以总的空间复杂度为 \(O(n)\),其中 n 是栈中元素的个数。
- 主栈
栈的压入、弹出序列
一、题目描述
-
给定两个整数序列
pushV
和popV
,pushV
表示栈的压入顺序,需要判断popV
是否为该栈可能的弹出顺序。 -
约束条件为:两个序列长度相等且在
0
到1000
之间,pushV
中的元素取值范围是-1000
到1000
且所有元素互不相同。
二、解题思路
-
辅助栈模拟:利用一个辅助栈
s
来模拟栈的操作。通过遍历pushV
和popV
序列,将pushV
中的元素依次压入辅助栈,在合适的时机尝试弹出栈顶元素与popV
中的元素进行匹配。 -
双指针遍历:使用两个指针,
i
用于遍历popV
,j
用于遍历pushV
。 -
入栈操作:在遍历
popV
时,只要栈为空或者栈顶元素不等于当前popV[i]
,就持续将pushV[j]
压入栈中并移动j
指针,直到满足栈顶元素等于popV[i]
或者j
到达pushV
的末尾。 -
出栈匹配:当栈顶元素等于
popV[i]
时,弹出栈顶元素,表示该元素成功匹配弹出顺序;如果栈顶元素始终不等于popV[i]
,则说明popV
不是合法的弹出顺序,返回false
。 -
最终判断:如果遍历完
popV
后,所有元素都能成功匹配弹出顺序,则返回true
。
三、代码
#include <vector>
#include <stack>class Solution {
public:bool IsPopOrder(std::vector<int> pushV, std::vector<int> popV) {int n = pushV.size();// 辅助栈std::stack<int> s;// 遍历入栈的下标int j = 0;// 遍历出栈的数组for (int i = 0; i < n; i++) {// 入栈:栈为空或者栈顶不等于出栈数组while (j < n && (s.empty() || s.top() != popV[i])) {s.push(pushV[j]);j++;}// 栈顶等于出栈数组if (s.top() == popV[i]) {s.pop();} // 不匹配序列else {return false;}}return true;}
};
- 初始化变量:获取
pushV
的长度n
,初始化辅助栈s
和遍历pushV
的指针j
。 - 遍历
popV
:在for
循环中遍历popV
,对于每个popV[i]
,通过while
循环将pushV
中的元素压入栈,直到栈顶元素等于popV[i]
或者pushV
遍历完。 - 匹配判断:如果栈顶元素等于
popV[i]
,弹出栈顶元素;否则,说明popV
不是合法弹出顺序,返回false
。 - 返回结果:遍历完
popV
后,若所有元素都匹配,返回true
。
四、复杂度分析
- 时间复杂度:由于每个元素最多入栈和出栈一次,所以时间复杂度为 \(O(n)\),其中
n
是序列的长度。 - 空间复杂度:在最坏情况下,辅助栈需要存储所有的元素,所以空间复杂度为 \(O(n)\)。
用队列实现栈
一、题目要求
题目要求使用队列来实现栈的功能,需要实现栈的基本操作,包括push
(入栈)、pop
(出栈)、top
(获取栈顶元素)和empty
(判断栈是否为空)。
二、解题思路
-
数据结构选择:使用两个队列
q1
和q2
来模拟栈。队列的特点是先进先出(FIFO),而栈的特点是后进先出(LIFO),通过巧妙地操作两个队列来实现栈的特性。 -
push
操作:-
每次有新元素
x
要入栈时,先将其放入q2
队列。 -
然后把
q1
队列中所有元素依次取出并放入q2
队列,这样q2
队列的顺序就变为了栈的顺序(后进先出)。 -
最后交换
q1
和q2
,使得q1
成为存储栈元素的队列,q2
为空,方便下一次push
操作。这样保证每次新元素都在q1
的队首,即栈顶位置。
-
-
pop
操作:-
直接取出
q1
的队首元素,因为经过push
操作后,q1
的队首元素就是栈顶元素。 -
取出后将其从
q1
中删除,实现栈的出栈操作。
-
-
top
操作:-
直接返回
q1
的队首元素,该元素即为栈顶元素。
-
-
empty
操作:-
通过判断
q1
是否为空来确定栈是否为空。如果q1
为空,说明栈中没有元素,返回true
;否则返回false
。
-
三、代码实现
#include <queue>class MyStack {
public:MyStack() {}void push(int x) {q2.push(x);while (!q1.empty()) {q2.push(q1.front());q1.pop();} std::swap(q1, q2); }int pop() {int r = q1.front();q1.pop();return r;}int top() {int r = q1.front();return r;}bool empty() {return q1.empty();}
private:std::queue<int> q1, q2;
};
-
构造函数
MyStack()
:目前构造函数为空,可用于初始化一些必要的状态,但在这个实现中暂时没有需要初始化的特殊内容。 -
push(int x)
函数:完成元素入栈操作,如上述思路中所述,通过两个队列的操作和交换来保证新元素在栈顶位置。 -
pop()
函数:返回并删除栈顶元素,即q1
的队首元素。 -
top()
函数:返回栈顶元素,也就是q1
的队首元素。 -
empty()
函数:判断栈是否为空,通过检查q1
是否为空来实现。
四、复杂度分析
-
时间复杂度:
-
push
操作:每次push
操作中,将q1
的元素转移到q2
的过程,时间复杂度为\(O(n)\),其中n是当前栈中元素的数量(因为需要遍历q1
中的所有元素)。其他操作pop
、top
和empty
的时间复杂度均为\(O(1)\),因为它们只涉及对队列的简单操作(取队首、判断是否为空)。
-
-
空间复杂度:
-
总共使用了两个队列
q1
和q2
,在最坏情况下,两个队列中存储的元素总数与栈中元素数量相同,所以空间复杂度为O(n),n为栈中元素的最大数量。
-
栈实现队列
一、题目要求
使用两个栈来实现一个队列的功能,需要实现队列的基本操作,包括 push
(入队)、pop
(出队)、peek
(查看队首元素)和 empty
(判断队列是否为空)。
二、解题思路
-
数据结构选择:选择两个栈
a
和b
来模拟队列的行为。栈的特点是后进先出(LIFO),而队列的特点是先进先出(FIFO),通过巧妙地操作这两个栈来实现队列的特性。 -
push
操作:-
每次有新元素
x
要入队时,直接将其压入栈a
中。因为栈a
用于存储新加入的元素,所以新元素入栈的操作非常简单直接。
-
-
pop
操作:-
先调用
peek
函数获取队首元素(即最早入队的元素)。这是因为在peek
函数中会确保队首元素在栈b
的栈顶。 -
然后从栈
b
中弹出该元素,实现出队操作。这样就保证了先进先出的队列特性。
-
-
peek
操作:-
首先检查栈
b
是否为空。如果栈b
不为空,说明队首元素就在栈b
的栈顶,直接返回栈b
的栈顶元素。 -
如果栈
b
为空且栈a
也为空,说明队列中没有元素,返回 -1。 -
如果栈
b
为空但栈a
不为空,需要将栈a
中的所有元素依次弹出并压入栈b
中。这样做的目的是将栈a
中最早入队的元素转移到栈b
的栈顶,以符合队列先进先出的特性。最后返回栈b
的栈顶元素,即队首元素。
-
-
empty
操作:-
判断队列是否为空,只需检查栈
a
和栈b
是否都为空。如果两个栈都为空,说明队列中没有元素,返回true
;否则返回false
。
-
三、代码实现
#include <stack>class MyQueue {
public:MyQueue() {}void push(int x) {a.push(x);}int pop() {int peek = this->peek();b.pop();return peek;}int peek() {if (!b.empty()) {return b.top();}if (a.empty()) {return -1;}while (!a.empty()) {b.push(a.top());a.pop();}int ret = b.top();return ret;}bool empty() {return a.empty() && b.empty();}private:std::stack<int> a, b;
};
-
构造函数
MyQueue()
:目前构造函数为空,可用于初始化一些必要的状态,但在这个实现中暂时没有需要初始化的特殊内容。 -
push(int x)
函数:将新元素x
压入栈a
中,实现入队操作。 -
pop()
函数:先调用peek
函数获取队首元素,然后从栈b
中弹出该元素,返回队首元素,实现出队操作。 -
peek()
函数:按照上述思路,检查栈b
和栈a
的状态,将元素在两个栈之间转移,最终返回队首元素。 -
empty()
函数:判断栈a
和栈b
是否都为空,返回相应的结果,以确定队列是否为空。
四、复杂度分析
-
时间复杂度:
-
push
操作:时间复杂度为 \(O(1)\),因为只是将元素压入栈a
,操作简单直接。 -
pop
操作:时间复杂度在平均情况下为 \(O(1)\),但在某些情况下(当栈b
为空且需要将栈a
的元素转移到栈b
时),时间复杂度为 \(O(n)\),其中n
是栈a
中元素的数量。 -
peek
操作:时间复杂度在平均情况下为 \(O(1)\),但在某些情况下(当栈b
为空且需要将栈a
的元素转移到栈b
时),时间复杂度为 \(O(n)\),其中n
是栈a
中元素的数量。 -
empty
操作:时间复杂度为 \(O(1)\),因为只需要检查两个栈是否为空。
-
-
空间复杂度:
-
总共使用了两个栈
a
和b
,在最坏情况下,两个栈中存储的元素总数与队列中元素数量相同,所以空间复杂度为 \(O(n)\),其中n
为队列中元素的最大数量。
-