一、核心原理讲解
1. 双栈实现队列
队列的核心是先进先出(FIFO) ,栈是后进先出(LIFO)。利用两个栈的"元素倒腾"特性,将栈的LIFO转换为队列的FIFO:
- 入栈(inStack):专门接收入队元素,保证入队操作O(1)。
- 出栈(outStack):专门弹出队首元素;若出栈为空,将入栈的所有元素弹出并压入出栈(此时入栈的栈底元素变为出栈的栈顶,符合FIFO)。
- 每个元素最多被倒腾一次,均摊时间复杂度O(1)。
2. 双队列实现栈
栈的核心是后进先出(LIFO) ,队列是先进先出(FIFO)。利用两个队列的"元素迁移"特性,将队列的FIFO转换为栈的LIFO:
- 主队列(queue1):保存栈的所有元素,入栈操作直接添加到主队列,O(1)。
- 辅助队列(queue2):临时存储迁移元素;出栈时将主队列除最后一个元素外的所有元素移到辅助队列,主队列剩余的最后一个元素即为栈顶,弹出后交换主/辅队列引用。
- 每个元素最多被迁移一次,均摊时间复杂度O(1)。
二、代码实现(每行注释+详解)
1. 双栈实现队列(StackQueue)
java
import java.util.Deque;
import java.util.LinkedList;
/**
* 双栈实现队列
* 核心:入栈存元素,出栈弹队首;出栈空时将入栈元素倒腾到出栈
*/
public class StackQueue {
// 入栈:接收入队元素(用Deque+LinkedList,替代不推荐的Stack类)
private final Deque<Integer> inStack;
// 出栈:弹出队首元素
private final Deque<Integer> outStack;
/**
* 构造方法:初始化两个栈
*/
public StackQueue() {
inStack = new LinkedList<>(); // LinkedList实现Deque,支持栈操作
outStack = new LinkedList<>();
}
/**
* 入队操作:元素压入入栈
* @param x 入队元素
*/
public void offer(int x) {
inStack.push(x); // 栈顶压入,O(1)
}
/**
* 出队操作:弹出队首元素
* @return 队首元素
* @throws IllegalStateException 队列为空时抛异常
*/
public int poll() {
// 出栈为空时,将入栈所有元素倒腾到出栈
if (outStack.isEmpty()) {
while (!inStack.isEmpty()) {
// 入栈弹出元素,压入出栈(反转元素顺序)
outStack.push(inStack.pop());
}
}
// 倒腾后仍为空,说明队列为空
if (outStack.isEmpty()) {
throw new IllegalStateException("队列已空,无法出队");
}
// 弹出出栈栈顶(即队首)
return outStack.pop();
}
/**
* 获取队首元素(不弹出)
* @return 队首元素
* @throws IllegalStateException 队列为空时抛异常
*/
public int peek() {
// 逻辑同poll,仅不弹出元素
if (outStack.isEmpty()) {
while (!inStack.isEmpty()) {
outStack.push(inStack.pop());
}
}
if (outStack.isEmpty()) {
throw new IllegalStateException("队列已空,无法获取队首");
}
// 获取出栈栈顶(队首)
return outStack.peek();
}
/**
* 判断队列是否为空
* @return 空返回true,否则false
*/
public boolean isEmpty() {
// 两个栈都为空,队列才为空(元素可能分布在任意栈)
return inStack.isEmpty() && outStack.isEmpty();
}
// 测试方法
public static void main(String[] args) {
StackQueue queue = new StackQueue();
queue.offer(1); // inStack: [1]
queue.offer(2); // inStack: [2,1]
queue.offer(3); // inStack: [3,2,1]
System.out.println(queue.poll()); // 倒腾inStack到outStack → outStack: [1,2,3],弹出1 → 输出1
System.out.println(queue.peek()); // outStack: [2,3],获取栈顶2 → 输出2
queue.offer(4); // inStack: [4]
System.out.println(queue.poll()); // 弹出outStack栈顶2 → 输出2
System.out.println(queue.poll()); // 弹出outStack栈顶3 → 输出3
System.out.println(queue.poll()); // outStack空,倒腾inStack的4 → 弹出4 → 输出4
System.out.println(queue.isEmpty()); // 两个栈都空 → 输出true
}
}
2. 双队列实现栈(QueueStack)
java
import java.util.LinkedList;
import java.util.Queue;
/**
* 双队列实现栈
* 核心:主队列存元素,出栈时迁移主队列元素到辅助队列,保留最后一个元素为栈顶
*/
public class QueueStack {
// 主队列:保存栈的所有元素(非final,需交换引用)
private Queue<Integer> queue1;
// 辅助队列:临时存储迁移的元素
private Queue<Integer> queue2;
/**
* 构造方法:初始化两个队列
*/
public QueueStack() {
queue1 = new LinkedList<>(); // LinkedList实现Queue,支持队列操作
queue2 = new LinkedList<>();
}
/**
* 入栈操作:元素添加到主队列
* @param x 入栈元素
*/
public void push(int x) {
queue1.offer(x); // 队列尾部添加,O(1)
}
/**
* 出栈操作:弹出栈顶元素(最后入栈的元素)
* @return 栈顶元素
* @throws IllegalStateException 栈为空时抛异常
*/
public int pop() {
// 主队列为空,栈为空
if (queue1.isEmpty()) {
throw new IllegalStateException("栈已空,无法出栈");
}
// 迁移主队列除最后一个元素外的所有元素到辅助队列
while (queue1.size() > 1) {
queue2.offer(queue1.poll());
}
// 主队列剩余的唯一元素即为栈顶
int top = queue1.poll();
// 交换主/辅队列引用(辅助队列变主队列,原主队列变空辅助队列)
Queue<Integer> temp = queue1;
queue1 = queue2;
queue2 = temp;
// 返回栈顶元素
return top;
}
/**
* 获取栈顶元素(不弹出)
* @return 栈顶元素
* @throws IllegalStateException 栈为空时抛异常
*/
public int peek() {
if (queue1.isEmpty()) {
throw new IllegalStateException("栈已空,无法获取栈顶");
}
// 迁移主队列除最后一个元素外的所有元素到辅助队列
while (queue1.size() > 1) {
queue2.offer(queue1.poll());
}
// 获取栈顶元素(主队列唯一元素)
int top = queue1.peek();
// 栈顶元素也移到辅助队列(避免丢失)
queue2.offer(queue1.poll());
// 交换队列引用,恢复主队列
Queue<Integer> temp = queue1;
queue1 = queue2;
queue2 = temp;
return top;
}
/**
* 判断栈是否为空
* @return 空返回true,否则false
*/
public boolean isEmpty() {
// 主队列为空,栈即为空(辅助队列仅临时存储)
return queue1.isEmpty();
}
// 测试方法
public static void main(String[] args) {
QueueStack stack = new QueueStack();
stack.push(1); // queue1: [1]
stack.push(2); // queue1: [1,2]
stack.push(3); // queue1: [1,2,3]
System.out.println(stack.pop()); // 迁移1、2到queue2 → queue1: [3],弹出3 → 输出3;交换后queue1: [1,2]
System.out.println(stack.peek()); // 迁移1到queue2 → queue1: [2],获取2;移2到queue2 → 交换后queue1: [1,2] → 输出2
stack.push(4); // queue1: [1,2,4]
System.out.println(stack.pop()); // 迁移1、2到queue2 → queue1: [4],弹出4 → 输出4;交换后queue1: [1,2]
System.out.println(stack.pop()); // 迁移1到queue2 → queue1: [2],弹出2 → 输出2;交换后queue1: [1]
System.out.println(stack.pop()); // queue1: [1],弹出1 → 输出1;交换后queue1: []
System.out.println(stack.isEmpty()); // queue1为空 → 输出true
}
}
三、关键总结
| 实现方式 | 核心思想 | 入操作时间复杂度 | 出操作均摊时间复杂度 | 空间复杂度 |
|---|---|---|---|---|
| 双栈队列 | 入栈存、出栈弹,空则倒腾 | O(1) | O(1) | O(n) |
| 双队列栈 | 主队列存、辅队列迁,保留最后一个 | O(1) | O(1) | O(n) |
- 双栈队列的核心是元素倒序:通过两个栈的反转,将栈的LIFO转换为队列的FIFO。
- 双队列栈的核心是元素迁移:通过辅助队列过滤出最后入队的元素,实现栈的LIFO。
- 均摊时间复杂度为O(1)的原因:每个元素仅被倒腾/迁移一次,后续操作无需重复处理。