本文整理来源 《轻松学算法——互联网算法面试宝典》/赵烨 编著
用栈实现队列
由于栈和队列的特殊顺序存储结构,一些面试官会出一些题目,比如用栈实现队列和用队列实现栈。
这样的题目在实际工作中并不具有实际应用意义,完全是为了考察大家的思考能力。
用两个栈实现队列
一般会用两个栈来实现队列。首先,我们将这两个栈分别定义为stack1和stack2
方案1
我们让入队操作在stack1中执行,而出队操作在stack2中执行。
- 入队:直接向stack1入栈
- 出队:将stack1中的所有元素出栈,依次入栈到stack2中,然后弹出stack2中的stack2中的栈顶元素,接着把stack2中的所有元素出现,依次压入stack1中。
来回入栈、出队比较繁琐,尤其是出队比较麻烦,需要先将元素从stack1倒入stack2里,然后stack2弹出元素之后又倒回stack1里。
方案2
入队都在stack1中进行,stack2用于出队,同时保证所有元素都在一个栈里,且遵循以下规则:
- 入队:不管stack1是否为空栈,都将stack2中的元素压入stack1中。
- 出队:若stack2不为空栈,则直接从stack2中弹出元素;若stack2为空栈,则把stack1中的元素倒入stack2中,再从stack2中弹出元素;若两个栈都是空的,则说明队列为空队,不能出队。
这种思路与方案1一样,只不过把倒回去的这个操作放在入队时执行,却使连续入队、出队的效率提高了。
方案3
入队都在stack1中操作,出队在stack2中操作,同时遵循以下规则:
- 入队:直接把元素压入stack1中。
- 出队:如果stack2不为空,则直接弹出stack2中的元素;如果stack2为空,则将stack1中的是所有元素带入stack2;如果stack2为空,则将stack1中的所有元素倒入stack2中,然后弹出stack2中的栈顶元素。同样若两个栈都是空栈,则队列为空队,无法出栈。
这个方案在入队时非常简单,而在出队时,多数情况下可以直接通过出队stack2实现,若需要把stack1中的元素倒入stack2中,则一般不用每次都进行操作。最坏的情况就是出队一个元素、入队一个元素这样循环操作,导致每次出队都要转移元素。
三个方案的操作时一样的。总体来说方案3是非常好的方案。
public class Stack2Queue<T> {
private Stack<T> stack1;
private Stack<T> stack2;
private int maxLength;
public Stack2Queue(int capacity) {
maxLength = capacity;
stack1 = new Stack<>(capacity);
stack2 = new Stack<>(capacity);
}
public boolean put(T item){
if (stack1.isFull() || maxLength == size()){
//满了
return false;
}
stack1.push(item);
return true;
}
public T poll(){
if (!stack2.isEmpty()){
return stack2.pop();
}else {
while (!stack1.isEmpty()){
stack2.push(stack1.pop());
}
return stack2.pop();
}
}
private int size() {
return stack1.size() + stack2.size();
}
}
测试代码:
public class Stack2QueueTest {
@Test
public void main(){
Stack2Queue<Integer> queue = new Stack2Queue<>(5);
queue.put(1);
queue.put(2);
Assert.assertEquals(1,(int)queue.poll());
queue.put(3);
queue.put(4);
Assert.assertEquals(2,(int)queue.poll());
Assert.assertEquals(3,(int)queue.poll());
}
}
用两个队列实现栈
栈的主要操作就是入栈和出栈,其特点就是后进先出。
我们先将两个队列分别定义为queue1与queue2。
方案1
入栈和出栈,都是queue1中完成,而queue2作为中转空间
- 入栈:直接入queue1即可。
- 出栈: 把queue1中除最后一个元素外的所有元素都移动到queue2中,再将queue1中的元素出队,此时即出栈;紧接着queue2中的所有元素移动到queue1中
这种操作过程与用栈实现队列的方案1一样,都是把第2个结构当做一个中转站,然后将数据来回倒。
方案2
从方案1中可以看到,在出栈时要把queue2中的元素移动到queue1中。在两个队列之间能否不用每次先出栈再把元素移动回去?
- 入栈:两个队列哪个不为空,就把元素入队到哪个队列中;如果都为空,则任选一个队列入队,假设这个队列就是queue1.
- 出栈:把不为空的队列中除最后一个元素外的所有元素移动到另一个队列中,然后出队最后一个元素。
public class Queue2Stack<T> {
private ArrayQueue<T> queue1;
private ArrayQueue<T> queue2;
private int maxLength;
public Queue2Stack(int capacity){
maxLength = capacity;
queue1 = new ArrayQueue<>(capacity);
queue2 = new ArrayQueue<>(capacity);
}
/**
* 入栈
* @param item 入栈元素
* @return 入栈结果
*/
public boolean push(T item){
if(size() == maxLength){
return false;
}
//如果中转空间为空,可以直接放入。当需要出栈时,放入元素即为队头
if (queue2.isEmpty()){
queue1.put(item);
}else {
queue2.put(item);
}
return true;
}
/**
* 出栈
* @return 出栈元素
*/
public T pop(){
if (size() == 0){
throw new IndexOutOfBoundsException("栈里空了");
}else {
if (queue2.isEmpty()){
while (queue1.size() > 1){
queue2.put(queue1.poll());
}
return queue1.poll();
}else {
while (queue2.size() > 1){
queue1.put(queue2.poll());
}
return queue2.poll();
}
}
}
private int size() {
return queue1.size() + queue2.size();
}
}
测试代码:
public class Queue2StackTest {
@Test
public void main() {
Queue2Stack<Integer> stack = new Queue2Stack<>(5);
stack.push(1);
stack.push(2);
Assert.assertEquals(2, (int) stack.pop());
stack.push(3);
stack.push(4);
Assert.assertEquals(4, (int) stack.pop());
Assert.assertEquals(3, (int) stack.pop());
}
}