双端队列(Deque)原理及实现

简介: 双端队列支持在队头和队尾高效地插入、删除元素,时间复杂度均为O(1)。相比标准队列的“先进先出”,它更灵活,类似两端可进出的过街天桥。可用链表或环形数组实现,常用于算法题中模拟栈或队列。

基本原理
如果你理解了前面讲解的内容,这个双端队列其实没啥可讲的了。所谓双端队列,主要是对比标准队列(FIFO 先进先出队列)多了一些操作罢了:
class MyDeque {
// 从队头插入元素,时间复杂度 O(1)
void addFirst(E e);

// 从队尾插入元素,时间复杂度 O(1)
void addLast(E e);

// 从队头删除元素,时间复杂度 O(1)
E removeFirst();

// 从队尾删除元素,时间复杂度 O(1)
E removeLast();

// 查看队头元素,时间复杂度 O(1)
E peekFirst();

// 查看队尾元素,时间复杂度 O(1)
E peekLast();

}
标准队列 只能在队尾插入元素,队头删除元素,而双端队列的队头和队尾都可以插入或删除元素。
普通队列就好比排队买票,先来的先买,后来的后买;而双端队列就好比一个过街天桥,两端都可以随意进出。当然,双端队列的元素就不再满足「先进先出」了,因为它比较灵活嘛。
在做算法题的场景中,双端队列用的不算很多。感觉只有 Python 用到的多一些,因为 Python 标准库没有提供内置的栈和队列,一般会用双端队列来模拟标准队列。
用链表实现双端队列
很简单吧,直接复用 我们之前实现的MyLinkedList 或者标准库提供的 LinkedList 就行了。因为双链表本就支持 O(1) 时间复杂度在链表的头尾增删元素:
import java.util.LinkedList;

public class MyListDeque {
private LinkedList list = new LinkedList<>();

// 从队头插入元素,时间复杂度 O(1)
void addFirst(E e) {
    list.addFirst(e);
}

// 从队尾插入元素,时间复杂度 O(1)
void addLast(E e) {
    list.addLast(e);
}

// 从队头删除元素,时间复杂度 O(1)
E removeFirst() {
    return list.removeFirst();
}

// 从队尾删除元素,时间复杂度 O(1)
E removeLast() {
    return list.removeLast();
}

// 查看队头元素,时间复杂度 O(1)
E peekFirst() {
    return list.getFirst();
}

// 查看队尾元素,时间复杂度 O(1)
E peekLast() {
    return list.getLast();
}

public static void main(String[] args) {
    MyListDeque<Integer> deque = new MyListDeque<>();
    deque.addFirst(1);
    deque.addFirst(2);
    deque.addLast(3);
    deque.addLast(4);

    System.out.println(deque.removeFirst()); // 2
    System.out.println(deque.removeLast()); // 4
    System.out.println(deque.peekFirst()); // 1
    System.out.println(deque.peekLast()); // 3
}

}
用数组实现双端队列
也很简单吧,直接复用我们在 环形数组技巧 中实现的 CycleArray 提供的方法就行了。环形数组头尾增删元素的复杂度都是 O(1):
class MyArrayDeque {
private CycleArray arr = new CycleArray<>();

// 从队头插入元素,时间复杂度 O(1)
void addFirst(E e) {
    arr.addFirst(e);
}

// 从队尾插入元素,时间复杂度 O(1)
void addLast(E e) {
    arr.addLast(e);
}

// 从队头删除元素,时间复杂度 O(1)
E removeFirst() {
    return arr.removeFirst();
}

// 从队尾删除元素,时间复杂度 O(1)
E removeLast() {
    return arr.removeLast();
}

// 查看队头元素,时间复杂度 O(1)
E peekFirst() {
    return arr.getFirst();
}

// 查看队尾元素,时间复杂度 O(1)
E peekLast() {
    return arr.getLast();
}

}

相关文章
|
1天前
|
搜索推荐
冒泡排序与其它排序算法比较
冒泡、选择、插入排序时间复杂度均为O(n²)。冒泡稳定,可优化至O(n),交换频繁;选择不稳定,交换次数少;插入稳定,对有序数组高效,三者中交换最少。相较其他高级排序无时间优势。
|
1天前
|
存储 JSON 编解码
RPC 实战:剖析 gRPC 源码,动手实现一个完整的 RPC
本讲通过剖析 gRPC 源码,深入讲解 RPC 框架的实现原理。从 Protocol Buffer 接口定义到 Stub 生成,结合 Netty 实现网络通信,解析请求的序列化、Frame 封装及 HTTP/2 多路复用机制,带你动手实现一个完整 RPC,掌握高性能框架设计核心。
|
22小时前
|
Java 测试技术 Linux
生产环境发布管理
本文介绍大型团队如何通过自动化部署平台实现多环境(dev→test→pre→prod)高效发布,涵盖各环境职责、基于Jenkins+K8S的CI/CD流程、分支管理与热更新机制,并结合Skywalking日志链路追踪快速定位问题,提升发布效率与系统稳定性。
|
1天前
|
人工智能 测试技术 API
Minion框架早已实现PTC:超越传统Tool Calling的Agent架构
Minion框架早于Anthropic的PTC特性,率先采用“LLM规划+代码执行”架构,通过Python编排工具调用,显著降低Token消耗、提升性能与可靠性。支持完整生态、状态管理与多模型协作,已在生产环境验证大规模数据处理、多源整合等复杂任务,推动AI Agent迈向高效、可扩展的下一代架构。
|
1天前
|
存储 缓存 算法
学习数据结构和算法的框架思维
本文系统梳理数据结构与算法核心思想:所有数据结构本质为数组或链表的变形,基本操作均为遍历与访问;算法本质是穷举,关键在于“无遗漏”和“无冗余”。掌握框架思维,方能以不变应万变,高效刷题。
学习数据结构和算法的框架思维
|
1天前
|
存储 数据可视化 Java
用拉链法实现哈希表
本文详解哈希表中拉链法的实现原理,通过简化版与完整版Java代码,介绍如何用链表解决哈希冲突,支持泛型、动态扩容及增删查改操作,帮助深入理解哈希表底层机制。
|
1天前
|
存储 缓存 算法
哈希表核心原理
哈希表不等于Map。Map是键值映射的抽象接口,哈希表(如HashMap)是其基于数组和哈希函数的具体实现之一。增删查改O(1)的性能依赖于哈希函数效率与冲突处理,而Map其他实现(如TreeMap)复杂度可能为O(logN)。需注意哈希冲突、扩容、负载因子及key不可变性等核心问题。
|
1天前
|
存储 算法 Java
动态数组代码实现
本文详解动态数组的底层实现,涵盖自动扩缩容、索引越界检查与内存泄漏防范三大关键点,结合Java代码演示增删查改操作及扩容机制,帮助理解数据结构设计原理。
|
1天前
|
算法 数据可视化
二叉树的递归/层序遍历 递归遍历(DFS)
本文详解二叉树的遍历框架,涵盖递归遍历的固定访问顺序及前、中、后序的本质区别——即代码在递归函数中的位置不同所致。同时深入讲解层序遍历(BFS)的三种实现方式,适用于不同场景,尤其适合求最短路径问题;而DFS则因结构天然适合搜索所有路径。通过实例对比,阐明BFS与DFS的应用偏好及原理依据。
二叉树的递归/层序遍历 递归遍历(DFS)
|
1天前
|
存储 算法 索引
二叉树基础及常见类型
二叉树是最核心的数据结构之一,不仅是红黑树、堆、字典树等复杂结构的基础,更体现了递归思维的本质。掌握二叉树,等于掌握算法解题的钥匙。从满二叉树到完全二叉树,再到二叉搜索树,各类变体应用广泛。其链式存储与哈希表表示法在算法题中灵活实用,是刷题进阶的必经之路。