232. 用栈实现队列
文章目录
一、题目
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push
、pop
、peek
、empty
):
实现 MyQueue
类:
void push(int x)
将元素 x 推到队列的末尾int pop()
从队列的开头移除并返回元素int peek()
返回队列开头的元素boolean empty()
如果队列为空,返回true
;否则,返回false
说明:
- 你 只能 使用标准的栈操作 —— 也就是只有
push to top
,peek/pop from top
,size
, 和is empty
操作是合法的。 - 你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
示例 1:
输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]
解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false
提示:
1 <= x <= 9
- 最多调用
100
次push
、pop
、peek
和empty
- 假设所有操作都是有效的 (例如,一个空的队列不会调用
pop
或者peek
操作)
进阶:
- 你能否实现每个操作均摊时间复杂度为
O(1)
的队列?换句话说,执行n
个操作的总时间复杂度为O(n)
,即使其中一个操作可能花费较长时间。
二、解法
算法思路
该代码实现了一个队列(MyQueue)的数据结构,使用两个栈(stack)来实现队列的操作。栈stIn用于存储插入操作(push)的元素,栈stOut用于存储弹出操作(pop)的元素。当需要执行弹出操作时,如果栈stOut为空,则将栈stIn中的元素逐个弹出并压入栈stOut,然后从栈stOut中弹出元素。这样就实现了先进先出的队列操作。
具体实现
- 在构造函数MyQueue()中,不需要执行任何操作,因为栈stIn和栈stOut会在插入和弹出操作中被初始化。
- 在插入操作push(int x)中,将元素x压入栈stIn。
- 在弹出操作pop()中,首先判断栈stOut是否为空。如果为空,则将栈stIn中的元素逐个弹出并压入栈stOut,然后从栈stOut中弹出元素并返回。
- 在查看队头元素操作peek()中,调用已有的pop()函数获取栈stOut的顶部元素,将该元素保存到变量res中。由于pop()函数已经弹出了元素res,所以需要将该元素再次压入栈stOut,然后返回res。
- 在判断队列是否为空操作empty()中,如果栈stIn和栈stOut都为空,则返回true;否则返回false。
class MyQueue {
public:
stack<int> stIn;
stack<int> stOut;
MyQueue() {
}
void push(int x) {
stIn.push(x);
}
int pop() {
if(stOut.empty()){
while(!stIn.empty()){
stOut.push(stIn.top());
stIn.pop();
}
}
int result = stOut.top();
stOut.pop();
return result;
}
int peek() {
//return stOut.top();
int res = this->pop(); // 直接使用已有的pop函数
stOut.push(res); // 因为pop函数弹出了元素res,所以再添加回去
return res;
}
bool empty() {
return (stIn.empty() && stOut.empty());
}
};
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue* obj = new MyQueue();
* obj->push(x);
* int param_2 = obj->pop();
* int param_3 = obj->peek();
* bool param_4 = obj->empty();
*/
算法分析
- push操作的时间复杂度为O(1),因为只需要将元素压入栈stIn。
- pop操作的时间复杂度为O(n),其中n是栈stIn中的元素数量。当栈stOut为空时,需要将栈stIn中的所有元素逐个弹出并压入栈stOut,因此时间复杂度为O(n)。但是,在连续的pop操作中,栈stOut中已经存储了之前的元素,所以每个元素只需要弹出一次,平均下来每个元素的弹出时间复杂度为O(1)。
- peek操作的时间复杂度为O(1),因为只需要调用已有的pop()函数一次。
- empty操作的时间复杂度为O(1),因为只需要判断栈stIn和栈stOut是否为空。
总结: 通过使用两个栈实现队列的操作,该代码实现了队列的插入、弹出、查看队头元素和判断队列是否为空的功能。插入操作的时间复杂度为O(1),弹出和查看队头元素的操作的平均时间复杂度为O(1),判断队列是否为空的操作的时间复杂度为O(1)。
三、一些拓展
拓展知识:使用栈实现队列的方法
使用两个栈实现队列的方法是一种常见的解法,称为"双栈法"。这种方法通过利用两个栈的先进后出(LIFO)和后进先出(FIFO)特性,实现了先进先出(FIFO)的队列操作。
使用双栈法实现队列的主要思想是,使用一个栈(stIn)来处理插入操作(push),将元素按照插入顺序压入栈stIn;使用另一个栈(stOut)来处理弹出操作(pop)和查看队头元素操作(peek)。当需要执行弹出或查看队头元素操作时,如果栈stOut为空,则将栈stIn中的元素逐个弹出并压入栈stOut,然后从栈stOut中弹出元素或查看队头元素。这样就实现了先进先出的队列操作。
这种双栈法的优点是插入操作(push)的时间复杂度为O(1),而弹出操作(pop)和查看队头元素操作(peek)的平均时间复杂度也为O(1),因为每个元素只需要弹出一次。另外,这种方法具有较好的空间复杂度,使用的额外空间仅为两个栈的大小。
在实际应用中,栈和队列都有各自的应用场景。栈常用于处理需要后进先出(LIFO)顺序的问题,如函数调用栈、括号匹配等。而队列则常用于处理需要先进先出(FIFO)顺序的问题,如任务调度、消息队列等。