双端队列与单调队列
如果说普通队列是单行道,只准进不准插队(FIFO),那么双端队列(Deque),就是一个双向通道,前后都能走人!而基于它诞生的单调队列(Monotonic Queue),作用也不容小视,能够解决很多经典难题。废话不多说,我们发车!
一、 为什么需要双端队列?
1. 普通队列的局限
- 就像食堂排队,只能从队尾进,从队头出。
- 想临时让队尾的人离开?或者让一个VIP插队到前面?对不起,规则不允许。
2. 双端队列的诞生
- 允许在队头和队尾进行插入和删除。
- 既能当队列用,也能当栈用。
以Web浏览器历史记录为例:
offerLast():你访问新页面,加到历史记录尾部。pollLast():你点“后退”,从尾部移除当前页面。pollFirst():历史记录满了,自动删除最老的记录。
二、 双端队列(Deque)
1. 双端队列是什么?一张图看懂!
双端队列是一种允许在线性表的两端进行插入和删除操作的数据结构。
操作全家福:
| 操作方向 | 操作类型 | 方法名示例 | 描述 |
|---|---|---|---|
| 队头(Head) | 插入 | addFirst(e), offerFirst(e) |
在队伍最前面加人 |
| 删除 | removeFirst(), pollFirst() |
队伍最前面的人离开 | |
| 查看 | getFirst(), peekFirst() |
看看队伍最前面是谁 | |
| 队尾(Tail) | 插入 | addLast(e), offerLast(e) |
在队伍最后面加人 |
| 删除 | removeLast(), pollLast() |
队伍最后面的人离开 | |
| 查看 | getLast(), peekLast() |
看看队伍最后面是谁 |
2. 底层实现:为什么选择双向链表?
理由:
- 单向链表:只能从头走到尾,想操作尾部需要O(n)时间
- 双向链表:每个节点都知道它的前驱和后继,头尾操作都是O(1)!
双向链表节点结构:
class ListNode {
int val; // 存储的值
ListNode prev; // 指向前一个节点
ListNode next; // 指向后一个节点
public ListNode(int val) {
this.val = val;
}
}
3. 核心实现:带哨兵节点的双向链表
为什么要用哨兵节点?
- 消除边界判断的麻烦!
- 永远有head和tail节点,代码更简介
初始化状态:
初始时,head的next指向tail,tail的prev指向head
代码实现核心部分:
public class LinkedListDeque {
// 哨兵节点
private ListNode head, tail;
private int size;
public LinkedListDeque() {
// 初始化两个哨兵节点,并互相连接
head = new ListNode(-1);
tail = new ListNode(-1);
head.next = tail;
tail.prev = head;
size = 0;
}
/**
* 队头插入 - 四步连接法
*/
public void offerFirst(int val) {
ListNode newNode = new ListNode(val);
ListNode firstRealNode = head.next;
// 第一步:head -> newNode
head.next = newNode;
newNode.prev = head;
// 第二步:newNode -> firstRealNode
newNode.next = firstRealNode;
firstRealNode.prev = newNode;
size++;
}
/**
* 队尾插入 - 镜像操作
*/
public void offerLast(int val) {
ListNode newNode = new ListNode(val);
// 当前最后一个真实节点
ListNode lastRealNode = tail.prev;
// 第一步:lastRealNode -> newNode
lastRealNode.next = newNode;
newNode.prev = lastRealNode;
// 第二步:newNode -> tail
newNode.next = tail;
tail.prev = newNode;
size++;
}
/**
* 队头删除
*/
public int pollFirst() {
if (isEmpty()) throw new RuntimeException("Deque is empty");
ListNode firstNode = head.next;
ListNode secondNode = firstNode.next;
// head直接指向第二个节点
head.next = secondNode;
secondNode.prev = head;
// 清理引用
firstNode.prev = null;
firstNode.next = null;
size--;
return firstNode.val;
}
}
offerFirst操作流程如下:


最低0.47元/天 解锁文章
1万+

被折叠的 条评论
为什么被折叠?



