Python面试高频考点:用deque手撕滑动窗口和BFS,附LeetCode真题解析
·
Python面试高频考点:用deque手撕滑动窗口和BFS,附LeetCode真题解析
1. 为什么deque是算法面试的"瑞士军刀"
在技术面试中,算法题往往考察候选人对数据结构的灵活运用能力。而Python的 collections.deque (双端队列)因其独特的特性,成为解决特定算法问题的利器。与列表(list)相比,deque在两端操作的时间复杂度均为O(1),这使得它在处理滑动窗口、BFS等问题时具有显著优势。
deque的核心优势对比 :
| 操作 | deque时间复杂度 | list时间复杂度 |
|---|---|---|
| 左端插入/删除 | O(1) | O(n) |
| 右端插入/删除 | O(1) | O(1) |
| 随机访问 | O(n) | O(1) |
从表中可以看出,当问题需要频繁在序列两端操作时,deque的性能优势尤为明显。这也是为什么在算法面试中,熟练使用deque往往能让你的解决方案脱颖而出。
2. 滑动窗口问题的deque解法精讲
滑动窗口是面试中最常见的算法范式之一,典型题目如LeetCode 239"滑动窗口最大值"。这类问题的核心在于高效维护窗口内的极值信息,而deque正是解决这类问题的理想数据结构。
2.1 滑动窗口最大值实战
让我们以LeetCode 239为例,详细拆解如何使用deque高效解决:
from collections import deque
def maxSlidingWindow(nums, k):
window = deque()
result = []
for i, num in enumerate(nums):
# 维护递减队列
while window and nums[window[-1]] < num:
window.pop()
window.append(i)
# 移除超出窗口范围的索引
if window[0] == i - k:
window.popleft()
# 当窗口形成时记录结果
if i >= k - 1:
result.append(nums[window[0]])
return result
关键点解析 :
- 我们使用deque存储的是元素索引而非值本身,这样可以方便判断元素是否在窗口内
- 维护一个"递减队列":新元素会"挤掉"队列中所有比它小的元素
- 队列头部始终是当前窗口的最大值
- 时间复杂度优化到O(n),而暴力解法是O(nk)
2.2 滑动窗口变种问题
掌握了基本模式后,可以轻松应对各种变种:
- 最小值窗口 :只需将递减队列改为递增队列
- 满足条件的子数组 :结合哈希表等数据结构扩展
- 多指针窗口 :使用多个deque维护不同条件
3. BFS与deque的完美结合
广度优先搜索(BFS)是图算法中的基础,而deque天然适合实现队列操作。在面试中,BFS类题目出现频率极高,从二叉树层序遍历到迷宫最短路径都属于这一范畴。
3.1 经典BFS模板
from collections import deque
def bfs(graph, start):
visited = set([start])
queue = deque([start])
while queue:
node = queue.popleft()
print(node) # 处理当前节点
for neighbor in graph[node]:
if neighbor not in visited:
visited.add(neighbor)
queue.append(neighbor)
模板要点 :
- 使用deque作为队列,保证先进先出
- visited集合防止重复访问
- 处理完当前节点后,将其未访问的邻居加入队列
3.2 层序遍历的进阶应用
LeetCode 102"二叉树的层序遍历"是BFS的典型应用:
def levelOrder(root):
if not root:
return []
result = []
queue = deque([root])
while queue:
level_size = len(queue)
current_level = []
for _ in range(level_size):
node = queue.popleft()
current_level.append(node.val)
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
result.append(current_level)
return result
面试技巧 :
- 记录每层大小(level_size)是关键
- 可以扩展解决锯齿形遍历、右视图等问题
- 时间复杂度O(n),空间复杂度O(n)
4. deque在其他算法场景的妙用
除了滑动窗口和BFS,deque还能优雅解决许多其他算法问题。
4.1 实现循环缓冲区
class CircularBuffer:
def __init__(self, size):
self.buffer = deque(maxlen=size)
def append(self, item):
self.buffer.append(item)
def get_items(self):
return list(self.buffer)
4.2 回文检查
def is_palindrome(s):
dq = deque(s.lower().replace(" ", ""))
while len(dq) > 1:
if dq.popleft() != dq.pop():
return False
return True
4.3 多级反馈队列调度
def scheduler(tasks, time_quantum):
queues = [deque() for _ in range(3)] # 3个优先级队列
current_time = 0
while any(queues) or tasks:
# 分配新任务到最高优先级队列
while tasks and tasks[0].arrival <= current_time:
queues[0].append(tasks.pop(0))
# 从高到低查找可执行任务
for i in range(3):
if queues[i]:
task = queues[i].popleft()
execute_time = min(time_quantum * (i+1), task.remaining)
# 执行任务...
task.remaining -= execute_time
current_time += execute_time
if task.remaining > 0:
# 降级到下一优先级队列
next_level = min(i+1, 2)
queues[next_level].append(task)
break
else:
current_time += 1
5. 面试中的避坑指南
在技术面试中使用deque时,有几个常见陷阱需要注意:
- 错误估计时间复杂度 :虽然deque操作是O(1),但某些复合操作可能不是
- 忽略边界条件 :特别是滑动窗口问题中窗口大小为0或大于数组长度的情况
- 过度使用deque :不是所有队列问题都需要deque,简单列表有时足够
- 线程安全误解 :虽然deque是线程安全的,但复合操作仍需加锁
高频面试问题 :
- deque和list的性能差异体现在哪些方面?
- 如何用deque实现一个阻塞队列?
- 滑动窗口问题中,为什么需要维护单调队列?
- BFS中为什么要用队列而不用栈?
更多推荐
所有评论(0)