这里写目录标题
前言
题目上的这两题可以说是经典题目了:
剑指 Offer 09: 双栈实现队列
leetcode 225: 队列实现栈
有关于栈和队列,我们之前也讨论过:
从零开始手撕一个数据结构(2)——基于链表实现栈和队列
我们都知道,栈和队列,一个是LIFO,一个是FIFO,那么怎么用栈去实现队列,用队列去实现栈呢?
源码
栈实现队列
我们知道,每次从栈中取一个元素都是从栈顶取的,如果我们要用栈实现一个队列,那这个【栈实现的队列】中每次【出队】的元素都是在栈底中取的,怎么样才能取到栈底元素呢?
剑指Offer 09 的题目要求是:用两个栈实现队列
思路
用两个栈的话就很好解决【如何取栈底元素】这个问题了:
- 将栈1的元素全部取到栈2,则栈2中存放元素的顺序和原来相反,也就是栈2的栈顶==栈1的栈底
- 我们使用栈1存储元素,在实例触发【出队】操作时,若栈2为空,将栈1中的全部元素取到栈2,再返回栈2的栈顶,若栈2不为空,则直接返回栈2的栈顶
- 在栈1,栈2都为空时,即代表【栈实现的队列】为空,依题意返回 -1
容器设计
题目要求是:
实现appendTail 和 deleteHead 两个方法, 分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
其中,appendTail
相当于队列中的offer
,即入队,而deleteHead
则相当于队列中的poll
,即出队,按照我们刚才的思路来,很容易就可以解决这个问题
类定义
public class StackImplQueue {
/**
* 这里用了java.util.Stack的栈,在速度上会比较慢,用LinkedList会快些,但没必要^_^
*/
Stack<Integer> s1;
Stack<Integer> s2;
public StackImplQueue() {
s1 = new Stack<>();
s2 = new Stack<>();
}
}
这里用了Stack,不用其它容器是因为其它容器的实现乱七八糟的
方法实现
1. 栈的“倒腾”
刚才我们的思路中,有将栈1元素全部取出放到栈2的操作,我称其为【栈的“倒腾”】,这就像你把一箱玩具倒到另一箱,原本在压箱底的玩具就会在另一箱的箱顶,具体操作如下
/**
* 将栈1倒腾到栈2上的方法
*/
public void stackTransfer() {
while (!s1.isEmpty()) {
s2.push(s1.pop());
}
}
这里栈2一直接受栈1的出栈元素,直到栈1为空,应该很好理解吧?
2. 入队操作
/**
* 添加操作时,往栈1添加元素
*
* @param value
*/
public void appendTail(int value) {
s1.push(value);
}
前面说了,栈1就是用来存东西的,所以入队时直接在栈1入栈就行了
3.出队操作
/**
* 删除操作时,当两个栈都为空,返回-1
* 当栈2为空,把栈1的元素倒腾到栈2上,再用栈2出栈(栈反转就是队列)
*
* @return
*/
public int deleteHead() {
if (s1.isEmpty() && s2.isEmpty()) {
return -1;
}
if (s2.isEmpty()) {
stackTransfer();
}
return s2.pop();
}
和刚才的操作一样,只有当栈2取空的时候才需要倒腾栈1,不然就乱序了
队列实现栈
那么,用队列如何实现栈呢?先看看【leetcode 225: 队列实现栈】的题目要求:
你只能使用队列的基本操作-- 也就是 push to back, peek/pop from front, size, 和 is empty 这些操作是合法的。
你所使用的语言也许不支持队列。 你可以使用 list 或者 deque(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
你可以假设所有操作都是有效的(例如, 对一个空的栈不会调用 pop 或者 top 操作)。
题目只对队列的操作做了要求,对实现方法并无特殊要求,既然刚才都用两个栈实现队列了,这次也用两个队列实现栈吧
思路
- 队列是FIFO的数据结构,要像栈一样出栈,取的元素就是队列的队尾
- 容器初始化后,我们将数据装入队列1中,触发出栈时,将队列1中的数据逐个出队装入队列2中,直到队列1剩下最后一个数据时,返回这个仅剩的数据
- 此时数据在队列2中
- 为保证数据的换队有序——数据在队列1中时要把数据换到队列2,反之亦然,容器维护一个布尔类型的状态值
boolean status
,当status==false
时,往队列1装数据,反之则往队列2装数据 - 每次触发出栈时都会对
status
进行取反操作,以表示当前是哪个队列在存储数据
容器设计
题目要求:
使用队列实现栈的下列操作:
push(x) – 元素 x 入栈
pop() – 移除栈顶元素
top() – 获取栈顶元素
empty() – 返回栈是否为空
只需要设计这四个方法就行了
类定义
public class QueueImplStack {
Queue<Integer> q1;
Queue<Integer> q2;
int size;
/**
* 容器状态,当status==false时,q1为元素放置队列,否则q2为元素放置队列
*/
boolean status;
/** Initialize your data structure here. */
public QueueImplStack() {
this.q1 = new LinkedList<>();
this.q2 = new LinkedList<>();
}
}
- 两条队列q1,q2
- size字段记录容器存储的元素个数
- status字段即刚才说的容器状态
- 构造方法不对size和status字段赋值,默认为零值,即0、false
方法实现
1. 最简单的empty方法
/** Returns whether the stack is empty. */
public boolean empty() {
return size==0;
}
2. 入栈方法
/** Push element x onto stack. */
public void push(int x) {
// 向元素放置队列添加新元素
if(status){
q2.offer(x);
}else{
q1.offer(x);
}
++size;
}
当status为false时,向队列1入队,反之向队列2入队
3. 队列转换方法
由于是两个队列实现的栈,数据的移动也是必须的
/**
* 队列转换,将当前放置元素所用的队列poll到另一条空队列,直到当前队列仅剩一个元素
* pop/top操作取栈顶元素时使用
*/
private void queueTransfer(){
for(int tmpSize=size-1;tmpSize>0;--tmpSize){
if(status){
q1.offer(q2.poll());
}else{
q2.offer(q1.poll());
}
}
}
获取size的个数再减1,然后循环出队入队
4. 出栈方法
/** Removes the element on top of the stack and returns that element. */
public int pop() {
queueTransfer();
// 根据状态,取当前放置元素队列仅剩的一个元素并出队
int res = status?q2.poll():q1.poll();
// 更改状态,使当前放置元素队列更改为另一队列
status=!status;
--size;
return res;
}
- 每次出栈时触发队列转换
- 再取当前放置元素队列中仅剩的元素出队
- 更改容器状态以标识新的放置元素队列
- 维护size
5. 取栈顶元素方法
/** Get the top element. */
public int top() {
queueTransfer();
// 根据状态,取当前放置元素队列仅剩的一个元素
int res = status?q2.peek():q1.peek();
// 将当前放置元素队列的最后一个元素移动到另一队列
if(status){
q1.offer(q2.poll());
}else{
q2.offer(q1.poll());
}
// 更改状态,使当前放置元素队列更改为另一队列
status=!status;
return res;
}
与出栈方法基本一致,只不过这里不对队尾元素进行出队,而是用peek取值之后再将其放到另一队列
总结
由于在 leetcode 都通过了,就不测试了
这两题是比较经典的题目,我的方法也不是最好的,有更好方法的同学欢迎留言交流^_^