代码随想录算法训练营第八天 | 232.用栈实现队列、225.用队列实现栈
代码随想录算法训练营第八天 | 232.用栈实现队列、225.用队列实现栈
文章目录
1 栈的理论基础
1.1 栈的基本概念
(1)定义
栈(Stack)是一种重要的数据结构,具有先进后出(First In Last Out, FILO)的特点,在栈结构中,最后加入的元素将会首先被移除,这种结构类似于现实生活中的一摞盘子,最后放上去的盘子会首先被取走。
(2)基本操作
- 入栈(Push):将一个元素放入栈顶。
- 出栈(Pop):移除并返回栈顶元素。
- 查看栈顶元素(Peek):返回栈顶元素而不移除它。
- 判断栈空:检查栈是否为空。
(3)栈的数学性质
n个不同元素进栈,出栈元素不同排列的个数为$\C_n = \frac{1}{n+1}\binom{2n}{n} = \frac{(2n)!}{(n+1)!n!} $上述公式被称为卡特兰数(Catalan number),这个知识点408也曾经考察过。
1.2 栈的存储结构
(1)顺序存储结构
这种栈使用数组来存储元素,它有一个指针指向栈顶,每次入栈或出栈操作都会更新这个指针,顺序栈的优点是简单易于实现,缺点是大小固定,可能会有空间浪费或空间不足的情况。
(2)链式存储结构
这种栈通过链表来实现,每个节点包含数据和指向下一个节点的指针,栈顶元素是链表的头节点,链式栈的优点是可以动态地扩展大小,但其操作相对于数组要复杂一些。
更多的介绍可以参考王道数据结构的第三章内容。
1.3 在Python中的栈实现
由于Python中没有专门的栈数据结构,但可以使用列表(List)来模拟栈,列表自带的方法可以很方便地实现栈的基本操作:
- 入栈:使用
append()
方法。 - 出栈:使用
pop()
方法,它默认移除并返回最后一个元素,即栈顶元素。 - 查看栈顶元素:通过索引访问列表的最后一个元素,例如
list[-1]
。 - 判断栈空:检查列表长度,使用
len(list)
,或直接判断list
是否为空。
class Stack:
def __init__(self):
self.items = [] # 使用列表存储栈的元素
def is_empty(self):
"""检查栈是否为空"""
return len(self.items) == 0
def push(self, item):
"""入栈操作"""
self.items.append(item)
def pop(self):
"""出栈操作,返回并移除栈顶元素"""
if not self.is_empty():
return self.items.pop()
return None # 如果栈为空,则返回None
def peek(self):
"""查看栈顶元素"""
if not self.is_empty():
return self.items[-1]
return None
def size(self):
"""返回栈的大小"""
return len(self.items)
stack = Stack()
stack.push('apple')
stack.push('banana')
print(stack.peek()) # 输出 'banana'
print(stack.pop()) # 输出 'banana'
print(stack.size()) # 输出 1
1.4 栈的应用
栈在计算机科学中应用广泛,例如:
- 函数调用栈:在程序中调用函数时使用栈来管理。
- 表达式求值:如编译器中的算术和逻辑表达式求值。
- 撤销机制:如文本编辑器中的撤销操作。
2 队列的理论基础
2.1 队列的基本概念
(1)定义
队列(Queue)也是计算机科学中的一种基本数据结构,它也遵循先进先出(First In First Out, FIFO)的原则。在队列中,元素被添加到一端(通常称为"尾部")并从另一端(“头部”)移除。这类似于现实生活中人们排队等候的情形,先来的人将先得到服务。
(2)基本操作
- 入队(Enqueue):将一个元素添加到队列尾部。
- 出队(Dequeue):移除并返回队列头部的元素。
- 查看队首元素(Front/Peek):返回队列头部的元素,但不移除它。
- 检查队列是否为空(IsEmpty):判断队列是否没有元素。
- 获取队列大小(Size):返回队列中元素的数量。
2.2 队列的存储结构
(1)顺序存储结构
使用数组实现,有一个指针指向队头,另一个指针指向队尾,当进行入队或出队操作时,相关指针会相应地移动,这种实现方式可能涉及到元素的搬移以避免空间的浪费(即队列的"环形"实现)。
(2)链式存储结构
使用链表实现,链表的头部作为队头,尾部作为队尾,入队操作在链表尾部进行,出队操作在头部进行,链式队列不需要搬移元素,因此更适合实现动态队列。
更多的介绍可以参考王道数据结构的第三章内容。
2.3 在Python中的队列实现
在Python中,可以使用列表(List)来模拟队列,但由于列表的头部操作(如插入和删除)是时间消耗较大的,因此在需要高效队列操作时,建议使用collections.deque
,它是一个双端队列,提供了在两端快速添加和删除元素的能力。
from collections import deque
class Queue:
def __init__(self):
self.items = deque() # 使用deque来存储队列元素
def is_empty(self):
"""检查队列是否为空"""
return len(self.items) == 0
def enqueue(self, item):
"""入队操作"""
self.items.append(item)
def dequeue(self):
"""出队操作,返回并移除队首元素"""
if not self.is_empty():
return self.items.popleft()
return None
def front(self):
"""查看队首元素"""
if not self.is_empty():
return self.items[0]
return None
def size(self):
"""返回队列的大小"""
return len(self.items)
queue = Queue()
queue.enqueue('apple')
queue.enqueue('banana')
print(queue.front()) # 输出 'apple'
print(queue.dequeue()) # 输出 'apple'
print(queue.size()) # 输出 1
2.4 队列的应用
队列在计算机科学中的应用同样非常广泛,包括但不限于:
- 数据缓冲(Buffering):如打印机任务队列。
- 任务调度:操作系统中的任务调度。
- 广度优先搜索(BFS)算法:在图和树的遍历中使用。
3 LeetCode 232.用栈实现队列
题目链接:https://leetcode.cn/problems/implement-queue-using-stacks/description/
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(
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)
,即使其中一个操作可能花费较长时间。
这道题目是一道模拟题,也就是考察我们对栈和队列的理解,如何使用栈来模拟队列?很容易就能想到,如果我们只用一个栈是肯定不够的,所以我们需要使用两个栈来实现,因为队列是一端输入,一端输出(这里我们默认不是双端队列),因此我们就需要一个输入栈和一个输出栈,输入栈接收输入的元素,此时出栈顺序与出队顺序相反,然后我们再把出栈的元素依次再次压入输出栈中(注意:是全部元素都要压栈,如果不是则会打乱输出顺序,可以举例模拟一下就清楚了),然后再出栈,这样输出的顺序就和队列的输出顺序一致了。
(1)Python版本代码
class MyQueue:
def __init__(self):
self.stack1 = [] # 输入栈
self.stack2 = [] # 输出栈
self.front = None # 队首元素
def push(self, x: int) -> None:
if not self.stack1: # 若输入栈为空,则x为队首元素
self.front = x
self.stack1.append(x)
def pop(self) -> int:
if not self.stack2:
while self.stack1: # 若输出栈为空,则将输入栈元素全部转移到输出栈
self.stack2.append(self.stack1.pop())
return self.stack2.pop()
def peek(self) -> int:
if not self.stack2:
return self.front
return self.stack2[-1] # 输出栈的栈顶元素即为队首元素
def empty(self) -> bool:
return not (self.stack1 or self.stack2) # 若输入栈和输出栈都为空,则队列为空
# Your MyQueue object will be instantiated and called as such:
# obj = MyQueue()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.peek()
# param_4 = obj.empty()
(2)C++版本代码
class MyQueue {
public:
stack<int> stIn;
stack<int> stOut;
MyQueue() {
}
void push(int x) {
stIn.push(x);
}
int pop() {
// 只有当stOut为空的时候,再从stIn里导入数据(导入stIn全部数据)
if (stOut.empty()) {
// 从stIn导入数据直到stIn为空
while(!stIn.empty()) {
stOut.push(stIn.top());
stIn.pop();
}
}
int result = stOut.top();
stOut.pop();
return result;
}
int peek() {
int res = this->pop(); // 直接使用已有的pop函数
stOut.push(res); // 因为pop函数弹出了元素res,所以再添加回去
return res;
}
bool empty() {
return stIn.empty() && stOut.empty();
}
};
- 时间复杂度: push和empty为O(1), pop和peek为O(n)
- 空间复杂度: O(n)
4 LeetCode 225.用队列实现栈
题目链接:https://leetcode.cn/problems/implement-stack-using-queues/description/
请你仅使用两个队列实现一个后入先出(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
都保证栈不为空**进阶:**你能否仅用一个队列来实现栈。
如果沿用上一题的思路,我们仍然也可以使用两个队列来模拟栈的操作。
4.1 使用两个队列实现栈
使用两个队列实现栈的方法涉及将新元素始终入队到主队列(queue1),在执行出栈操作时,除了最后一个元素,将主队列中的所有元素转移到辅助队列(queue2)中,然后出队并返回主队列中的最后一个元素,即栈顶元素,之后将主队列和辅助队列的角色互换,使得辅助队列成为新的主队列,而原主队列则变为空。在查看栈顶元素时,执行与出栈相同的操作,但在返回栈顶元素之前,将其重新放回队列中。栈是否为空的判断则通过检查主队列是否为空来实现。
(1)Python版本代码
from collections import deque
class MyStack:
def __init__(self):
self.queue1 = deque() # 主队列,用于模拟栈
self.queue2 = deque() # 辅助队列,用于在出栈操作中临时存储元素
def push(self, x: int):
self.queue1.append(x) # 入栈操作,直接在队列尾部添加元素
def pop(self):
# 出栈操作
while len(self.queue1) > 1:
self.queue2.append(self.queue1.popleft()) # 将queue1的元素(除了最后一个)移到queue2
res = self.queue1.popleft() # 获取队列中的最后一个元素,即栈顶元素
self.queue1, self.queue2 = self.queue2, self.queue1 # 交换两个队列的角色
return res # 返回栈顶元素
def top(self):
# 获取栈顶元素
res = self.pop() # 先用pop方法取出栈顶元素
self.queue1.append(res) # 然后将其重新入队到queue1
return res # 返回栈顶元素
def empty(self):
# 检查栈是否为空
return not self.queue1 # 如果queue1为空,则栈为空
(2)C++版本代码
#include <queue>
using namespace std;
class MyStack {
public:
MyStack() {
// 构造函数,初始化时不需要做特别操作
}
void push(int x) {
queue1.push(x); // 入栈操作,直接在队列尾部添加元素
}
int pop() {
// 出栈操作
while (queue1.size() > 1) {
queue2.push(queue1.front()); // 将queue1的元素(除了最后一个)移到queue2
queue1.pop();
}
int res = queue1.front(); // 获取队列中的最后一个元素,即栈顶元素
queue1.pop(); // 移除该元素
swap(queue1, queue2); // 交换两个队列的角色
return res; // 返回栈顶元素
}
int top() {
// 获取栈顶元素
int res = this->pop(); // 先用pop方法取出栈顶元素
queue1.push(res); // 然后将其重新入队到queue1
return res; // 返回栈顶元素
}
bool empty() {
// 检查栈是否为空
return queue1.empty(); // 如果queue1为空,则栈为空
}
private:
queue<int> queue1; // 主队列,用于模拟栈
queue<int> queue2; // 辅助队列,用于在出栈操作中临时存储元素
};
4.2 使用一个队列实现栈
在使用单个队列实现栈的方法中,首先创建一个队列 queue
来存储栈元素,当一个新元素需要入栈时,它首先被添加到队列的末尾。接着队列中之前的所有元素(即在新元素之前入队的元素)被依次出队并立即重新入队到队列的末尾,这样新入队的元素就被移动到了队列的前面,这个过程模拟了栈的后进先出(LIFO)行为,出栈操作简单地出队队列的第一个元素,即为最后入栈的元素。查看栈顶元素时,只需返回队列的头部元素。判断栈是否为空的操作则通过检查队列是否为空来完成。
(1)Python版本代码
from collections import deque
class MyStack:
def __init__(self):
self.queue = deque() # 使用deque创建一个队列,用来存储栈内的元素
def push(self, x: int):
n = len(self.queue) # 记录当前队列中元素的数量
self.queue.append(x) # 将新元素入队到队列的末尾
for _ in range(n):
self.queue.append(self.queue.popleft()) # 将队列前面的元素依次出队并重新入队到末尾,以确保最新入队的元素位于队列的前面
def pop(self):
return self.queue.popleft() # 出栈操作,直接出队队列的第一个元素,即最后入栈的元素
def top(self):
return self.queue[0] # 返回队列的第一个元素,即栈顶元素
def empty(self):
return not self.queue # 检查队列是否为空,即栈是否为空
(2)C++版本代码
#include <queue>
using namespace std;
class MyStack {
public:
MyStack() {
// 构造函数,初始化时不需要特别操作
}
void push(int x) {
int n = queue.size(); // 获取当前队列中的元素数量
queue.push(x); // 将新元素入队到队列的末尾
for (int i = 0; i < n; ++i) {
queue.push(queue.front()); // 将队列前面的元素依次出队并重新入队到末尾
queue.pop(); // 完成元素的重新排列
}
}
int pop() {
int topElement = queue.front(); // 获取队列的第一个元素,即栈顶元素
queue.pop(); // 出队操作
return topElement; // 返回栈顶元素
}
int top() {
return queue.front(); // 返回队列的第一个元素,即栈顶元素
}
bool empty() {
return queue.empty(); // 检查队列是否为空
}
private:
queue<int> queue; // 使用单个队列来存储栈内的元素
};
原文地址:https://blog.csdn.net/qq_52417436/article/details/135713869
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!