栈与队列
- 栈 (stack) :所有元素必须符合后进先出(LIFO)规则
- 队列 (queue):所有元素必须符合先进先出(FIFO)规则
常用 api
list.pop(index)
会返回list[index]
,同时在list
内删除 index 对应的元素
list.pop()
默认弹出最后一个元素
deque
deque 是 python collections 中的子类,具体表现为两端队列。
deque 可以有效处理 list 中特定操作复杂度过高的情况。
from collections import deque
以下只概括了与 stack、queue 有关的内容。
添加元素
append
执行正常 list 的操作,在 list 的末尾加入元素
appendleft
则是在 list 的开头加入元素
queue = collections.deque()
queue.append('a')
queue.append('b') # deque(['a', 'b'])
queue.appendleft('A')
queue.appendleft('B') # deque(['B', 'A', 'a', 'b'])
添加可迭代对象
queue.extend(['D', 'E']) # deque(['B', 'A', 'a', 'b', 'D', 'E'])
queue.extendleft(['c', 'd']) # deque(['d', 'c', 'B', 'A', 'a', 'b', 'D', 'E'])
"abc"
同样也是 iterable object。如果想将 "abc"
作为整体添加,可以将其作为 ["ABC"]
输入。
弹出元素
queue.pop() # 'E'
# deque(['d', 'c', 'B', 'A', 'a', 'b', 'D'])
queue.popleft() # 'd'
# deque(['c', 'B', 'A', 'a', 'b', 'D'])
获取值,不 pop
queue[0] # 'c'
# deque(['c', 'B', 'A', 'a', 'b', 'D'])
queue[-1] # ''
# deque(['c', 'B', 'A', 'a', 'b', 'D'])
deque 的大部分的 api 都和 list 非常相似。
232. 用栈实现队列
考验对于栈和队列的掌握,不涉及算法。
重点在如何实现顺序的反转:可以认为一个栈是 FILO,两个栈并用即是 FILO + LIFO = FIFO,即达成了队列的顺序。(非常抽象的概念)
- 定义一个输入栈,一个输出栈
- 输入栈(以栈的方式)储存输入的元素
- 当需要输出(pop)时,将输入栈中的所有元素以相反的顺序输入(pop + push)输出栈中
- 此时在输出栈中,元素的顺序就与 queue 中元素一样,pop 即可
FILO + LIFO = FIFO 中的朴素思想:一层栈的储存顺序对于队列来说是一次反转,那么两层栈的储存顺序对于队列来说是两次反转,负负得正即是队列的储存顺序。
以下动图来源于代码随想录。
class MyQueue:
def __init__(self):
self.in_stack = []
self.out_stack = []
def push(self, x: int) -> None:
self.in_stack.append(x)
def pop(self) -> int:
if self.out_stack == []:
for i in range(len(self.in_stack) - 1, -1, -1):
self.out_stack.append(self.in_stack[i])
self.in_stack = []
return self.out_stack.pop(-1)
def peek(self) -> int:
last_element = self.pop()
self.out_stack.append(last_element)
return last_element
def empty(self) -> bool:
return len(self.in_stack) + len(self.out_stack) == 0
# Your MyQueue object will be instantiated and called as such:
# obj = MyQueue()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.peek()
# param_4 = obj.empty()
225. 用队列实现栈
这道题看似和上一题没有区别,但是却没办法直接套用上一题的思路。重点在于队列本身不会扭转数据的顺序,也就是 FIFO + FIFO = FIFO。
实际上,一个队列即可实现顺序的逆转,只需要将 top 元素(除最后一个)弹出后再次加入即可实现逆向。队列在概念上是双向开口的(进出各一端),而栈在概念上是单向开口(进出共用一端)。
class MyStack:
def __init__(self):
self.queue = []
def push(self, x: int) -> None:
self.queue.append(x)
def pop(self) -> int:
if self.queue == []:
return None
for i in range(len(self.queue) - 1):
self.queue.append(self.queue.pop(0))
return self.queue.pop(0)
def top(self) -> int:
top_element = self.pop()
self.queue.append(top_element)
return top_element
def empty(self) -> bool:
return not len(self.queue)
# Your MyStack object will be instantiated and called as such:
# obj = MyStack()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.top()
# param_4 = obj.empty()
deque 解法
以上用 list 模拟队列的方法看上去很符合直觉,但要注意到 list.pop(0)
的复杂度是
O
(
n
)
O(n)
O(n),也就是说 self.pop
的复杂度是
O
(
n
2
)
O(n^2)
O(n2),实在是太高了。
可以借助 collections.deque
来作为队列的实例。通常来说 deque 的实现是链表,所以删除头元素只需要
O
(
1
)
O(1)
O(1),可以大大简化复杂度。
from collections import deque
class MyStack:
def __init__(self):
self.queue = deque()
def push(self, x: int) -> None:
self.queue.append(x)
def pop(self) -> int:
if self.empty():
return None
for i in range(len(self.queue) - 1):
self.queue.append(self.queue.popleft())
return self.queue.popleft()
def top(self) -> int:
top_element = self.pop()
self.queue.append(top_element)
return top_element
def empty(self) -> bool:
return not len(self.queue)