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

简介: 双端队列支持在队头和队尾高效地插入、删除和访问元素,时间复杂度均为O(1)。相比标准队列的“先进先出”,它更灵活,如同两端可进出的天桥。可用链表或环形数组实现,常用于需双向操作的场景,算法题中Python使用较多。

基本原理
如果你理解了前面讲解的内容,这个双端队列其实没啥可讲的了。所谓双端队列,主要是对比标准队列(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月前
|
Java 索引 容器
环形数组技巧
环形数组通过模运算在逻辑上形成闭环,利用start和end双指针实现首尾O(1)增删。虽物理结构线性,但通过取余操作使指针循环,结合左闭右开区间设计,高效支持动态扩容缩容,适用于队列、双端队列等场景。
|
1月前
|
存储 数据可视化 Java
用拉链法实现哈希表
本文详解哈希表中拉链法的实现原理,通过简化版与完整版Java代码,介绍如何用链表解决哈希冲突,支持泛型、动态扩容及增删查改操作,帮助深入理解哈希表底层机制。
|
1月前
|
Java API
用数组实现队列/栈
使用数组实现栈时,可将动态数组尾部作为栈顶,利用ArrayList的add和remove操作实现push、pop等,时间复杂度均为O(1)。若以头部为栈顶,则需借助环形数组CycleArray实现高效操作。同样,基于CycleArray可在首尾分别进行出队和入队,轻松实现队列功能,保证操作效率。
|
存储 算法
数据结构— —栈的基本操作(顺序栈和链栈)
数据结构— —栈的基本操作(顺序栈和链栈)
vue3 wangEditor富文本自定义上传本地图片
Vue3和WangEditor都提供了上传本地图片的功能,可以结合使用实现自定义上传本地图片。
1854 0
|
10月前
|
Java 数据库
【YashanDB知识库】kettle同步大表提示java内存溢出
在数据导入导出场景中,使用Kettle进行大表数据同步时出现“ERROR:could not create the java virtual machine!”问题,原因为Java内存溢出。解决方法包括:1) 编辑Spoon.bat增大JVM堆内存至2GB;2) 优化Kettle转换流程,如调整批量大小、精简步骤;3) 合理设置并行线程数(PARALLELISM参数)。此问题影响所有版本,需根据实际需求调整相关参数以避免内存不足。
|
监控 调度
队列的深度解析:链式队列的实现
队列的深度解析:链式队列的实现
|
监控 Java 开发者
Spring Cloud中的服务熔断与降级
Spring Cloud中的服务熔断与降级
|
设计模式 Java 容器
Java一分钟之-Swing基础:JFrame, JPanel, JButton
Java Swing教程介绍了构建桌面应用的关键组件:JFrame(顶级容器,显示主窗口)、JPanel(组合其他组件的容器)和JButton(交互元素)。文中通过示例代码展示了这些组件的使用,并列出常见问题及解决方法,如确保设置JFrame的可见性和关闭操作,正确添加组件至JPanel,以及为JButton添加事件监听器。理解这些基础将有助于开发功能完善的GUI应用。
780 0
Java的在类内部调用本类方法
Java的在类内部调用本类方法
1055 0