算法-每日一题(DAY15)用队列实现栈
1.题目链接
225. 用队列实现栈 - 力扣(LeetCode)
2.题目描述
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push
、top
、pop
和 empty
)。
实现 MyStack
类:
void push(int x)
将元素 x 压入栈顶。int pop()
移除并返回栈顶元素。int top()
返回栈顶元素。boolean empty()
如果栈是空的,返回true
;否则,返回false
。
注意:
- 你只能使用队列的标准操作 —— 也就是
push to back
、peek/pop from front
、size
和is empty
这些操作。 - 你所使用的语言也许不支持队列。 你可以使用 list (列表)或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
示例:
输入:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False
提示:
1 <= x <= 9
最多调用100 次 push、pop、top 和 empty
每次调用 pop 和 top 都保证栈不为空
3.解题思路
对于这道题我们使用了两个队列(q1 和 q2)的双队列策略。初始化时,两个队列为空。对于入栈操作(push),元素直接被推入队列 q1。出栈操作(pop)则通过将队列 q1 中的所有元素逐个转移到队列 q2 中,直到 q1 中只剩下一个元素,这样单独剩下的元素即为要弹出的栈顶元素。随后将 q2 中的元素全部转回 q1,保持队列的栈结构。获取栈顶元素(top)的方法直接返回 q1 的最后一个元素。检查栈是否为空(empty)则简单地返回 q1 是否为空。通过这种方法,栈的基本操作得以利用队列的特性有效地实现,灵活地保持了后进先出的原则。
更通俗的讲,想象你有两个箱子(q1 和 q2)。你将东西(元素)从箱子 q1 放入箱子 q2,但是你不想每次都手动把东西搬回去。所以每当你从栈里拿东西时,你就把 q1 里的东西搬到 q2,直到剩下一个东西,然后那就是栈顶元素。拿出栈顶元素后,把 q2 的东西再搬回 q1,让栈继续运作。
4.题解代码
class MyStack {
public:queue<int> q1, q2; // 定义两个队列 q1 和 q2,用于实现栈的功能MyStack()// 构造函数,初始化栈{}void push(int x)// push 函数:将元素 x 压入栈中{q1.push(x);// 直接将元素 x 添加到队列 q1 的末尾}int pop()// pop 函数:从栈中弹出元素并返回{// 将 q1 中的元素全部移到 q2,直到 q1 剩下最后一个元素while (q1.size() > 1) {// 将 q1 的前面的元素移动到 q2q2.push(q1.front());q1.pop();} int res = q1.front();// 保存 q1 中的最后一个元素(栈顶元素)q1.pop(); // 弹出栈顶元素(即来自 q1 的最后一个元素)q1 = q2;// 将 q2 中的元素全部移动回 q1while (!q2.empty()) {q2.pop();// 清空 q2,准备下一次操作}return res;// 返回被弹出的元素}int top()// top 函数:返回栈顶的元素但不弹出它{return q1.back();// 由于 q1 是在 push 时添加元素的,top 方法直接访问 q1 队列中的最后一个元素}bool empty()// empty 函数:检查栈是否为空{return q1.empty();// 通过检查 q1 是否为空来判断栈是否为空}
};
5.示例演算
步骤 | 操作 | 队列1状态 (队首← →队尾) | 队列2状态 (队首← →队尾) | 栈状态 (栈底← →栈顶) | 操作详情说明 |
---|---|---|---|---|---|
初始 | - | [] | [] | [] | 初始状态 |
1 | push(1) | [1] | [] | [1] | 将1加入队列1 |
2 | push(2) | [1,2] | [] | [1,2] | 将2加入队列1 |
3 | push(3) | [1,2,3] | [] | [1,2,3] | 将3加入队列1 |
4 | pop() | [] | [1,2] | [1,2] | 关键步骤: 1. 将队列1前n-1个元素(1,2)转移到队列2 2. 弹出队列1的最后一个元素(3) |
5 | push(4) | [] | [1,2,4] | [1,2,4] | 将4加入当前非空队列(队列2) |
6 | pop() | [1,2] | [] | [1,2] | 关键步骤: 1. 将队列2前n-1个元素(1,2)转移到队列1 2. 弹出队列2的最后一个元素(4) |
7 | pop() | [1] | [] | [1] | 关键步骤: 1. 将队列1前n-1个元素(1)转移到队列2 2. 弹出队列1的最后一个元素(2) |
6.复杂度计算
时间复杂度:
操作 | 时间复杂度 | 说明 |
---|---|---|
push | O(1) | 直接插入队列末尾 |
pop | O(n) | 需要转移n−1个元素 + 队列深拷贝O(n) + 清空队列O(n) |
top | O(1) | 直接访问队列尾部元素 |
empty | O(1) | 简单判断队列是否为空 |
空间复杂度:总空间复杂度为 O(n),因为两个队列最多同时存储 n 个元素(在执行 push 操作时会临时存储)。此外,额外空间复杂度为 O(1),因为指针的交换和操作不需要额外的存储开销。