ArrayDeque 比 LinkedList 更适合滑动窗口,因其头尾增删 O(1)、缓存友好、扩容可控;必须存索引而非值以判断越界,初始化需处理边界情况,避免冗余清空和错误遍历。

滑动窗口里为什么选 ArrayDeque 而不是 LinkedList
因为 ArrayDeque 在头尾增删都是 O(1) 且缓存友好,LinkedList 虽然也实现 Deque,但每次操作要新建节点、指针跳转,实际慢 2–3 倍,尤其在窗口频繁移动时更明显。
常见错误是看到“链表天然支持双端”就默认选 LinkedList,结果压测时 CPU 突增,其实是内存不连续导致的 cache miss 太多。
-
ArrayDeque底层是循环数组,扩容时复制成本可控;LinkedList每次addFirst都触发对象分配 - 如果窗口大小固定且已知上限(比如最大 10^5),可初始化容量:
new ArrayDeque(maxSize),避免多次扩容 - 别用
ArrayList手动模拟——头插add(0, x)是 O(n),直接卡死滑动窗口逻辑
ArrayDeque 存索引而不是元素值,这是关键
滑动窗口求最大/最小值时,队列里必须存下标(int),不是数值本身。否则无法判断某个值是否已滑出窗口边界。
典型场景:给定数组 nums 和窗口大小 k,求每个窗口最大值。若存值,当 nums[0] 被移出后,你根本不知道队首那个数对应哪个位置,也就无法安全弹出。
立即学习“Java免费学习笔记(深入)”;
- 入队前,从队尾往前踢掉所有 ≤ 当前元素的旧索引(维持单调递减)
- 检查队首索引是否
(即是否已不在当前窗口),是则 <code>pollFirst() - 队首始终是当前窗口最大值的下标:
nums[deque.peekFirst()]
容易被忽略的边界:空队列、单元素窗口、k=1
ArrayDeque 自身不会报错,但业务逻辑常在这里崩——比如没判空就调 peekFirst(),或 k > nums.length 时仍硬算。
错误现象:NullPointerException 或结果数组长度不对,调试时发现某轮循环没写入值。
- 初始化时若
k == 0或nums为空,直接返回空结果,别进主循环 - 窗口从下标
0开始,但第一个完整窗口结束位置是k - 1,所以有效结果从i = k - 1开始填 -
k == 1时,队列每轮只存一个索引,pollFirst()后立刻offerLast(),别漏掉任何一次offer
性能陷阱:别在循环里反复调 size() 判空
ArrayDeque.size() 是 O(1),这点没问题;但新手常写 while (!deque.isEmpty()) 配合 pollFirst() 来清空,这在滑动窗口中完全没必要,还拖慢逻辑。
真正该做的是“按需清理”:只在新元素入队前清理队尾无效项,只在取答案前检查队首是否越界。多一步无意义的清空,等于给每次迭代加冗余操作。
- 避免写:
while (deque.size() > k) deque.pollFirst();—— 窗口大小靠下标判断,不是靠队列长度 - 不要用
for (int x : deque)遍历队列取值 —— 这会触发内部迭代器创建,且顺序未必是你想要的 - 调试时想看内容?用
System.out.println(deque)就行,它输出的是从头到尾的元素序列
事情说清了就结束









