01栈和队列相互实现
栈和队列两种不同的数据结构,是相反的存取特性。栈遵循后进先出(LIFO)原则,队列遵循先进先出(FIFO)原则。
本文介绍如何用栈实现队列,以及如何用队列实现栈,这是数据结构的经典问题。
02栈实现队列
问题描述
LeetCode 232: https://leetcode.cn/problems/implement-queue-using-stacks/
给定只能使用标准栈结构,要求实现一个完整的队列功能,包括入队、出队、查看队头元素和判断空等操作。
核心思路
使用两个栈配合工作:
- in栈:负责接收新加入的元素
- out栈:负责提供队列的出队操作
关键在于数据转移的两个原则:
1. 转移条件:只有当out栈为空时,才能从in栈转移数据
2. 转移完整性:如果要转移数据,必须将in栈中的所有数据全部转移完
实现代码
/**
* 用栈实现队列
*/
class StackToQueue {
private Stack<Integer> inStack; // 负责入队操作
private Stack<Integer> outStack; // 负责出队操作
public StackToQueue() {
inStack = new Stack<>();
outStack = new Stack<>();
}
/**
* 数据转移方法
* 核心原则:
* 1. 只有outStack为空时才能转移
* 2. 转移时必须将inStack清空
*/
private void transferData() {
if (outStack.isEmpty()) {
while (!inStack.isEmpty()) {
outStack.push(inStack.pop());
}
}
}
/**
* 入队操作
* @param x 要入队的元素
*/
public void push(int x) {
transferData(); // 先尝试转移数据
inStack.push(x); // 新元素总放入inStack
}
/**
* 出队操作
* @return 队头元素
*/
public int pop() {
transferData(); // 先确保outStack弹出所有数据,再转移
return outStack.pop();
}
/**
* 查看队头元素(不弹出)
* @return 队头元素
*/
public int peek() {
transferData();
return outStack.peek();
}
/**
* 判断队列是否为空
*/
public boolean empty() {
return inStack.isEmpty() && outStack.isEmpty();
}
}
时间复杂度(均摊):
- push():O(1) - 均摊分析,每个元素最多进出栈4次
- pop():O(1) - 均摊分析
- peek():O(1) - 均摊分析
- empty():O(1) - 直接判断
空间复杂度:
- O(n) - 需要两个栈存储n个元素
03队列实现栈
问题描述
LeetCode 225: https://leetcode.cn/problems/implement-stack-using-queues/
给定只能使用标准队列结构,要求实现一个完整的栈功能,包括压栈、弹栈、查看栈顶元素和判断空等操作。
核心思路
使用一个队列即可实现。关键思想是在每次压栈时:
1. 记录新元素加入前队列的大小n
2. 将新元素加入队尾
3. 执行n次"队头出队→队尾入队"操作,将新元素调整到队头位置
这样队头始终是最后压入的元素,符合栈的后进先出特性。
实现代码
class QueueToStack {
private Queue<Integer> queue;
public QueueToStack() {
queue = new LinkedList<>();
}
/**
* 压栈操作
* 时间复杂度:O(n),因为重新排列队列中的n个元素
* @param x 要压入的元素
*/
public void push(int x) {
int size = queue.size(); // 记录加入新元素前的队列大小
queue.offer(x); // 新元素入队
// 将之前的所有元素重新排列,使新元素位于队头
for (int i = 0; i < size; i++) {
queue.offer(queue.poll());
}
}
/**
* 弹栈操作
*/
public int pop() {
return queue.poll();
}
/**
* 查看栈顶元素(不弹出)
* @return 栈顶元素
*/
public int top() {
return queue.peek();
}
/**
* 判断栈是否为空
*/
public boolean empty() {
return queue.isEmpty();
}
}
时间复杂度:
- push():O(n) - 需要重新排列队列中的n个元素
- pop():O(1) - 直接从队头弹出
- top():O(1) - 直接访问队头
- empty():O(1) - 直接判断
空间复杂度:
- O(n) - 需要一个队列存储n个元素
04实现难度分析
栈实现队列
1. 数据转移时机:只在out为空时转移
2. 转移完整性:转移时必须清空in栈
3. 所有操作均摊时间复杂度为O(1)
队列实现栈
1. 元素重排:新元素入队后立即重排到队头
2. 大小记录:准确记录重排前的队列大小,重排操作次数等于原队列大小
3. 压栈操作时间复杂度为O(n),性能较差,适用压栈操作较少的场合
文章参考:bilibili左程云算法系列
青轴作响,叨叨有声。
欢迎留言,一起分享你的技术见闻。每一篇都聊技术人的真实选择与成长。IT之路,不孤单。
声明:文章仅代表作者本人观点,供学习和交流使用。
公众号:叨叨猿的青轴日记
更多往期内容请在公众号回复[算法那些事]
合作/投稿:daodaoyuanblue@163.com