算法课堂——时光倒流(栈)
这是我参与2022首次更文挑战的第7天,活动详情查看:2022首次更文挑战」。
昨天我们学习了队列的基本概念,明白了什么是逻辑结构什么是物理结构,学习了队列为什么能进行历史重现,以及队列在一些典型题目中的应用。今天我们带大家学习一下在算法中经常和队列成对出现的数据结构--栈,让你明白为什么队列能都进行历史重现,而栈却能让时光倒流呢?
一、什么是栈?
栈其实和队列很相似,都是一种逻辑结构,并且也都可以分别用数组、链表来表示。不过栈和队列有一点不同的是,队列里面的元素,必须遵守先入先出的原则,即排队原则;但是栈看起来就不是那么公平了,栈里面的元素要遵循的是先入后出的原则。
大家看过腌咸菜吗?腌咸菜的时候是不是把晒好的咸菜一点一点塞入瓶子里;不过你仔细想一下,你在吃咸菜的时候是不是把最后放进去的咸菜先拿出来吃。这就是栈。所以我们以后就把栈叫做“咸菜罐”,哈哈。
二、栈的应用
我们今天的标题叫做时光倒流,我们结合栈先入后出的原则解释一下,栈为什么能够进行时光倒流,首先,我们后放入栈的元素可以立马拿出来,就等于我们有后悔药可以吃。比如刚才一步我们放错了元素,我们可以立马将元素取出,从而回到上一步,重新开始。
三、真题详解
我们从队列和栈的概念中知道,栈和队列都可以用数组、链表来实现,只不过队列是先入先出,栈是先入后出,那么我们首先想到的是,队列和栈是不是能够相互转化呢?说干就干。首先我们先用队列来实现栈
请你仅使用两个队列实现一个后入先出(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(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
如题所示,我们要用先入先出的队列来实现栈的先入后出,其实题目已经给了提示,用两个队列。那么这两个队列分别有什么用呢?
既然要实现栈,其中有一个队列在数据上肯定要和我们直接操作的栈的数据保持一致。
我们做一下模拟:
我们定义两个队列:
第一步我们在队列2中,放入第一个元素1
| 1 |
|---|
第二步将队列二作为队列一, 即我们要表示的栈(一个元素看不出来,我们接着看)
| 1 |
|---|
第三步我们在队列二中放入2,并将队列1中的元素取出放入队列2
| 2 | 1 |
|---|
第四步我们队列二作为队列一, 即我们要表示的栈(两个元素了)
| 2 | 1 |
|---|
第五步我们在队列二中放入3,并将队列1中的元素取出放入队列2
| 3 | 2 | 1 |
|---|
第六步我们队列二作为队列一, 即我们要表示的栈(三个元素了)
| 3 | 2 | 1 |
|---|
以此类推
此时我们看下我们的队列一,是不是和我们的栈一样,遵循先入后出的原则呢?
出栈很简单,将队列一的栈顶元素取出即可。
代码如下:
class MyStack {
Queue<Integer> queue1;
Queue<Integer> queue2;
//两个的队列
public MyStack() {
queue1 = new LinkedList<Integer>();
queue2 = new LinkedList<Integer>();
}
//两个队列的操作来满足栈的先入后出
public void push(int x) {
queue2.offer(x);
while (!queue1.isEmpty()) {
queue2.offer(queue1.poll());
}
Queue<Integer> temp = queue1;
queue1 = queue2;
queue2 = temp;
}
public int pop() {
return queue1.poll();
}
public int top() {
return queue1.peek();
}
//判断队列是否为空
public boolean empty() {
return queue1.isEmpty();
}
}
那么同学们可以借助这题,思考下如何用栈来实现队列吗?可以写在评论区哦。
话说回来了我们在什么问题中可以运用到栈的时光倒流的能力呢?由于时间关系,我们下节课用两道比较复杂的算法题带大家领略一下回溯的魅力。请看下一节: