队列实现栈(leetcode225)
题目描述:请你仅使用两个队列实现一个后入先出(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(双端队列)来模拟一个队列 , 只要是标准的队列操作即可。
示例:
输入:
["MyStack", "push", "push", "top", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 2, 2, false]
解释:
MyStack myStack = new MyStack();
myStack.push(1);
myStack.push(2);
myStack.top(); // 返回 2
myStack.pop(); // 返回 2
myStack.empty(); // 返回 False
思路:
- 定义两个队列,一个主队列,一个辅助队列,模拟栈后进先出。
- 当入栈操作时,先将该数据入队辅助队列,然后主队列内容导入辅助队列(while循环),最后将将主队列和辅助队列进行交换。
- 其实,了解了上述思路(将当前加入元素加到已经按照栈排好的元素最前边),我们只需要一个队列即可。
复杂度分析
-
时间复杂度:入栈操作 O(n),其余操作都是 O(1)。
入栈操作需要将queue 1中的 n个元素出队,并入队 n+1 个元素到queue 2 ,共有 2n+1 次操作,每次出队和入队操作的时间复杂度都是 O(1),因此入栈操作的时间复杂度是 O(n)。
出栈操作和获取栈顶元素对应将 queue 1的前端元素出队,时间复杂度是 O(1)。
判断栈是否为空操作只需要判断queue 1是否为空,时间复杂度是 O(1)。
-
空间复杂度:O(n),其中 n 是栈内的元素。需要使用两个队列存储栈内的元素。
代码实现:
class MyStack {
private Queue<Integer> queue1;
private Queue<Integer> queue2;
public MyStack() {
queue1 = new LinkedList<>();
queue2 = new LinkedList<>();
}
public void push(int x) {
queue2.offer(x);
// 特别注意,while循环
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();
}
}
使用一个队列实现(将当前加入的元素放在已经是栈顺序序列的最前边,实现后进先出原则而)
class MyStack {
private Queue<Integer> queue;
public MyStack() {
queue = new LinkedList<>();
}
public void push(int x) {
queue.offer(x);
int cnt = queue.size();
while (cnt-- > 1) {
queue.offer(queue.poll());
}
}
public int pop() {
return queue.poll();
}
public int top() {
return queue.peek();
}
public boolean empty() {
return queue.isEmpty();
}
}
栈实现队列(leetcode232)
问题描述:请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):实现 MyQueue 类:
- void push(int x) 将元素 x 推到队列的末尾
- int pop() 从队列的开头移除并返回元素
- int peek() 返回队列开头的元素
- boolean empty() 如果队列为空,返回 true ;否则,返回 false
说明:
- 你只能使用标准的栈操作 —— 也就是只有 push to top, peek/pop from top, size, 和 is empty 操作是合法的。你所使用的语言也许不支持栈。你可以使用 list 或者 deque(双端队列)来模拟一个栈,只要是标准的栈操作即可。
要求:
- 你能否实现每个操作均摊时间复杂度为 O(1) 的队列?换句话说,执行 n 个操作的总时间复杂度为 O(n) ,即使其中一个操作可能花费较长时间。
示例:
输入:
["MyQueue", "push", "push", "peek", "pop", "empty"]
[[], [1], [2], [], [], []]
输出:
[null, null, null, 1, 1, false]
解释:
MyQueue myQueue = new MyQueue();
myQueue.push(1); // queue is: [1]
myQueue.push(2); // queue is: [1, 2] (leftmost is front of the queue)
myQueue.peek(); // return 1
myQueue.pop(); // return 1, queue is [2]
myQueue.empty(); // return false
思路:
- 定义两个栈用于数据的输入和输出。
- 当进行pop和poll操作时,如果输出栈为空,那么将输入栈数据全部移到输出栈。所以,当移到输出栈中然后进行输出的时候(出队列),就满足先进先出的结构。
复杂度分析:
- 时间复杂度:push 和empty 为 O(1),pop 和 peek 为均摊 O(1)。对于每个元素,至多入栈和出栈各两次,故均摊复杂度为 O(1)。
- 空间复杂度:O(n)O(n)。其中 n是操作总数。对于有 n 次 push 操作的情况,队列中会有 n 个元素,故空间复杂度为 O(n)。
代码实现:
class MyQueue {
private Deque<Integer> inStack;
private Deque<Integer> outStack;
public MyQueue() {
inStack = new LinkedList<>();
outStack = new LinkedList<>();
}
public void push(int x) {
inStack.push(x);
}
// 获取栈中的元素时,需要检查输出栈是否为空
public int pop() {
if (outStack.isEmpty()) {
inToOut();
}
return outStack.pop();
}
public int peek() {
if (outStack.isEmpty()) {
inToOut();
}
return outStack.peek();
}
public boolean empty() {
return inStack.isEmpty() && outStack.isEmpty();
}
// 将输入栈中的元素移到输出栈
private void inToOut() {
while (!inStack.isEmpty()) {
outStack.push(inStack.pop());
}
}
}
对于均摊复杂度的说明:参考@宫水三叶
我们先用另外一个例子来理解「均摊复杂度」,大家都知道「哈希表」底层是通过数组实现的。
- 正常情况下,计算元素在哈希桶的位置,然后放入哈希桶,即插入操作复杂度为 O(1),假定是通过简单的“拉链法”搭配「头插法」方式来解决哈希冲突。
- 但当某次元素插入后,「哈希表」达到扩容阈值,则需要对底层所使用的数组进行扩容,这个复杂度是 O(n)
- 显然「扩容」操作不会发生在每一次的元素插入中,因此扩容的 O(n) 都会伴随着 n 次的 O(1),也就是 O(n) 的复杂度会被均摊到每一次插入当中,因此哈希表插入仍然是 O(1)的。
同理,我们不是发生在每一次的「输出操作」中,而是集中发生在一次「输出栈为空」的时候,因此 pop 和 peek 都是均摊复杂度为 O(1)的操作。
补充:Queue接口分析:add和offer区别,remove和poll方法到底啥区别
这些方法的主要区别在于,如果执行失败,是抛出异常还是返回值。
- queue的增加元素方法add和offer的区别在于,add方法在队列满的情况下将选择抛异常的方法来表示队列已经满了,而offer方法通过返回false表示队列已经满了;在有限队列的情况,使用offer方法优于add方法;
- remove方法和poll方法都是删除队列的头元素,remove方法在队列为空的情况下将抛异常,而poll方法将返回null;
- element和peek方法都是返回队列的头元素,但是不删除头元素,区别在与element方法在队列为空的情况下,将抛异常,而peek方法将返回null
总结:我们一般希望抛出返回值,优先使用offer,poll和peek方法。
网友评论