标题:设计循环双端队列
出处:641. 设计循环双端队列
6 级
设计你的循环双端队列实现。
实现 MyCircularDeque \texttt{MyCircularDeque} MyCircularDeque 类:
你不允许使用内置的队列数据结构。
示例 1:
输入:
["MyCircularDeque", "insertLast", "insertLast", "insertFront", "insertFront", "getRear", "isFull", "deleteLast", "insertFront", "getFront"] \texttt{["MyCircularDeque", "insertLast", "insertLast", "insertFront", "insertFront", "getRear", "isFull", "deleteLast", "insertFront", "getFront"]} ["MyCircularDeque", "insertLast", "insertLast", "insertFront", "insertFront", "getRear", "isFull", "deleteLast", "insertFront", "getFront"]
[[3], [1], [2], [3], [4], [], [], [], [4], []] \texttt{[[3], [1], [2], [3], [4], [], [], [], [4], []]} [[3], [1], [2], [3], [4], [], [], [], [4], []]
输出:
[null, true, true, true, false, 2, true, true, true, 4] \texttt{[null, true, true, true, false, 2, true, true, true, 4]} [null, true, true, true, false, 2, true, true, true, 4]
解释:
MyCircularDeque myCircularDeque = new MyCircularDeque(3); \texttt{MyCircularDeque myCircularDeque = new MyCircularDeque(3);} MyCircularDeque myCircularDeque = new MyCircularDeque(3);
myCircularDeque.insertLast(1); \texttt{myCircularDeque.insertLast(1);} myCircularDeque.insertLast(1); // 返回 True \texttt{True} True
myCircularDeque.insertLast(2); \texttt{myCircularDeque.insertLast(2);} myCircularDeque.insertLast(2); // 返回 True \texttt{True} True
myCircularDeque.insertFront(3); \texttt{myCircularDeque.insertFront(3);} myCircularDeque.insertFront(3); // 返回 True \texttt{True} True
myCircularDeque.insertFront(4); \texttt{myCircularDeque.insertFront(4);} myCircularDeque.insertFront(4); // 返回 False \texttt{False} False,双端队列已满
myCircularDeque.getRear(); \texttt{myCircularDeque.getRear();} myCircularDeque.getRear(); // 返回 2 \texttt{2} 2
myCircularDeque.isFull(); \texttt{myCircularDeque.isFull();} myCircularDeque.isFull(); // 返回 True \texttt{True} True
myCircularDeque.deleteLast(); \texttt{myCircularDeque.deleteLast();} myCircularDeque.deleteLast(); // 返回 True \texttt{True} True
myCircularDeque.insertFront(4); \texttt{myCircularDeque.insertFront(4);} myCircularDeque.insertFront(4); // 返回 True \texttt{True} True
myCircularDeque.getFront(); \texttt{myCircularDeque.getFront();} myCircularDeque.getFront(); // 返回 4 \texttt{4} 4
这道题和「设计循环队列」相似,区别在于这道题需要设计的结构是双端队列,双端队列支持在队首和队尾添加元素和删除元素。
循环双端队列的实现有两种方式,分别是基于数组和基于双向链表。数组和双向链表本身并不是环形结构,在实现时需要模拟环形结构。
使用数组实现循环双端队列时,需要维护数组、循环双端队列的大小(即最多允许存储的元素个数)、队首下标、队尾下标和循环双端队列中的元素个数。其中,数组的长度为循环双端队列的大小,队首下标表示队首元素所在位置的下标,队尾下标表示队尾元素的后一个位置的下标。
构造方法中,将数组 deque \textit{deque} deque 初始化为长度 k k k 的数组,将循环双端队列的容量 capacity \textit{capacity} capacity 设为 k k k,将队首下标 head \textit{head} head 和队尾下标 tail \textit{tail} tail 都初始化为 0 0 0,将元素个数 size \textit{size} size 初始化为 0 0 0。
对于在队首添加元素操作,首先判断循环双端队列是否已满,如果循环双端队列已满则返回 false \text{false} false,如果循环双端队列未满则执行在队首添加元素操作。
令 head : = ( head + capacity − 1 ) m o d capacity \textit{head} := (\textit{head} + \textit{capacity} - 1) \bmod \textit{capacity} head:=(head+capacity−1)modcapacity,取余运算确保 head \textit{head} head 在下标范围内。
令 deque [ head ] : = value \textit{deque}[\textit{head}] := \textit{value} deque[head]:=value。
令 size : = size + 1 \textit{size} := \textit{size} + 1 size:=size+1。
返回 true \text{true} true。
由于 head \textit{head} head 是队首元素所在位置的下标,因此需要首先更新 head \textit{head} head 然后将 value \textit{value} value 赋给 deque [ head ] \textit{deque}[\textit{head}] deque[head]。
对于在队尾添加元素操作,首先判断循环双端队列是否已满,如果循环双端队列已满则返回 false \text{false} false,如果循环双端队列未满则执行在队尾添加元素操作。
令 deque [ tail ] : = value \textit{deque}[\textit{tail}] := \textit{value} deque[tail]:=value。
令 tail : = ( tail + 1 ) m o d capacity \textit{tail} := (\textit{tail} + 1) \bmod \textit{capacity} tail:=(tail+1)modcapacity,取余运算确保 tail \textit{tail} tail 在下标范围内。
令 size : = size + 1 \textit{size} := \textit{size} + 1 size:=size+1。
返回 true \text{true} true。
由于 tail \textit{tail} tail 是队尾元素的后一个位置的下标,因此需要首先将 value \textit{value} value 赋给 deque [ tail ] \textit{deque}[\textit{tail}] deque[tail] 然后更新 tail \textit{tail} tail。
对于在队首删除元素操作,首先判断循环双端队列是否为空,如果循环双端队列为空则返回 false \text{false} false,如果循环双端队列不为空则执行在队首删除元素操作。
令 head : = ( head + 1 ) m o d capacity \textit{head} := (\textit{head} + 1) \bmod \textit{capacity} head:=(head+1)modcapacity,取余运算确保 head \textit{head} head 在下标范围内。
令 size : = size − 1 \textit{size} := \textit{size} - 1 size:=size−1。
返回 true \text{true} true。
对于在队尾删除元素操作,首先判断循环双端队列是否为空,如果循环双端队列为空则返回 false \text{false} false,如果循环双端队列不为空则执行在队尾删除元素操作。
令 tail : = ( tail + capacity − 1 ) m o d capacity \textit{tail} := (\textit{tail} + \textit{capacity} - 1) \bmod \textit{capacity} tail:=(tail+capacity−1)modcapacity,取余运算确保 tail \textit{tail} tail 在下标范围内。
令 size : = size − 1 \textit{size} := \textit{size} - 1 size:=size−1。
返回 true \text{true} true。
对于获取队首元素和获取队尾元素操作,首先判断循环双端队列是否为空,如果循环双端队列为空则返回 − 1 -1 −1,如果循环双端队列不为空则返回对应下标处的元素。
对于获取队首元素操作,返回 deque [ head ] \textit{deque}[\textit{head}] deque[head]。
对于获取队尾元素操作,返回 deque [ ( tail + capacity − 1 ) m o d capacity ] \textit{deque}[(\textit{tail} + \textit{capacity} - 1) \bmod \textit{capacity}] deque[(tail+capacity−1)modcapacity]。
对于检查循环双端队列是否为空和检查循环双端队列是否已满操作,这两种情况下都有 head = tail \textit{head} = \textit{tail} head=tail,因此需要根据循环双端队列中的元素个数判断。
当且仅当 size = 0 \textit{size} = 0 size=0 时,循环双端队列为空。
当且仅当 size = capacity \textit{size} = \textit{capacity} size=capacity 时,循环双端队列已满。
检查循环双端队列是否为空和检查循环双端队列是否已满操作可以在其余的操作中复用。具体而言,在 insertFront \texttt{insertFront} insertFront 和 insertLast \texttt{insertLast} insertLast 中调用 isFull \texttt{isFull} isFull 判断循环双端队列是否已满,在 deleteFront \texttt{deleteFront} deleteFront、 deleteLast \texttt{deleteLast} deleteLast、 getFront \texttt{getFront} getFront 和 getRear \texttt{getRear} getRear 中调用 isEmpty \texttt{isEmpty} isEmpty 判断循环双端队列是否为空。
class MyCircularDeque {
int[] deque;
int capacity;
int head;
int tail;
int size;
public MyCircularDeque(int k) {
deque = new int[k];
capacity = k;
head = 0;
tail = 0;
size = 0;
}
public boolean insertFront(int value) {
if (isFull()) {
return false;
}
head = (head + capacity - 1) % capacity;
deque[head] = value;
size++;
return true;
}
public boolean insertLast(int value) {
if (isFull()) {
return false;
}
deque[tail] = value;
tail = (tail + 1) % capacity;
size++;
return true;
}
public boolean deleteFront() {
if (isEmpty()) {
return false;
}
head = (head + 1) % capacity;
size--;
return true;
}
public boolean deleteLast() {
if (isEmpty()) {
return false;
}
tail = (tail + capacity - 1) % capacity;
size--;
return true;
}
public int getFront() {
if (isEmpty()) {
return -1;
}
return deque[head];
}
public int getRear() {
if (isEmpty()) {
return -1;
}
return deque[(tail + capacity - 1) % capacity];
}
public boolean isEmpty() {
return size == 0;
}
public boolean isFull() {
return size == capacity;
}
}
时间复杂度:构造方法和每一项操作的时间复杂度是 O ( 1 ) O(1) O(1)。
空间复杂度: O ( k ) O(k) O(k),其中 k k k 是循环双端队列的大小。
由于循环双端队列需要支持在队首和队尾添加元素和删除元素,因此使用双向链表。使用双向链表实现循环双端队列时,需要维护队首结点、队尾结点、循环双端队列的大小(即最多允许存储的元素个数)和循环双端队列中的元素个数。
构造方法中,将队首结点 head \textit{head} head 和队尾结点 tail \textit{tail} tail 初始化为 null \text{null} null,将循环双端队列的容量 capacity \textit{capacity} capacity 设为 k k k,将元素个数 size \textit{size} size 初始化为 0 0 0。
对于在队首添加元素操作,首先判断循环双端队列是否已满,如果循环双端队列已满则返回 false \text{false} false,如果循环双端队列未满则执行在队首添加元素操作。
创建新结点 node \textit{node} node,新结点的值为 value \textit{value} value。
如果循环双端队列为空,则令 head : = node \textit{head} := \textit{node} head:=node 和 tail : = node \textit{tail} := \textit{node} tail:=node;如果循环双端队列不为空,则令 head . prev : = node \textit{head}.\textit{prev} := \textit{node} head.prev:=node 和 node . next : = head \textit{node}.\textit{next} := \textit{head} node.next:=head,然后令 head : = node \textit{head} := \textit{node} head:=node。
令 head . prev : = tail \textit{head}.\textit{prev} := \textit{tail} head.prev:=tail 和 tail . next : = head \textit{tail}.\textit{next} := \textit{head} tail.next:=head,形成循环结构。
令 size : = size + 1 \textit{size} := \textit{size} + 1 size:=size+1。
返回 true \text{true} true。
对于在队尾添加元素操作,首先判断循环双端队列是否已满,如果循环双端队列已满则返回 false \text{false} false,如果循环双端队列未满则执行在队尾添加元素操作。
创建新结点 node \textit{node} node,新结点的值为 value \textit{value} value。
如果循环双端队列为空,则令 head : = node \textit{head} := \textit{node} head:=node 和 tail : = node \textit{tail} := \textit{node} tail:=node;如果循环双端队列不为空,则令 tail . next : = node \textit{tail}.\textit{next} := \textit{node} tail.next:=node 和 node . prev : = tail \textit{node}.\textit{prev} := \textit{tail} node.prev:=tail,然后令 tail : = node \textit{tail} := \textit{node} tail:=node。
令 head . prev : = tail \textit{head}.\textit{prev} := \textit{tail} head.prev:=tail 和 tail . next : = head \textit{tail}.\textit{next} := \textit{head} tail.next:=head,形成循环结构。
令 size : = size + 1 \textit{size} := \textit{size} + 1 size:=size+1。
返回 true \text{true} true。
对于在队首删除元素操作,首先判断循环双端队列是否为空,如果循环双端队列为空则返回 false \text{false} false,如果循环双端队列不为空则执行在队首删除元素操作。
令 head : = head . next \textit{head} := \textit{head}.\textit{next} head:=head.next。
令 head . prev : = tail \textit{head}.\textit{prev} := \textit{tail} head.prev:=tail 和 tail . next : = head \textit{tail}.\textit{next} := \textit{head} tail.next:=head,形成循环结构。
令 size : = size − 1 \textit{size} := \textit{size} - 1 size:=size−1。
返回 true \text{true} true。
对于在队尾删除元素操作,首先判断循环双端队列是否为空,如果循环双端队列为空则返回 false \text{false} false,如果循环双端队列不为空则执行在队尾删除元素操作。
令 tail : = tail . prev \textit{tail} := \textit{tail}.\textit{prev} tail:=tail.prev。
令 head . prev : = tail \textit{head}.\textit{prev} := \textit{tail} head.prev:=tail 和 tail . next : = head \textit{tail}.\textit{next} := \textit{head} tail.next:=head,形成循环结构。
令 size : = size − 1 \textit{size} := \textit{size} - 1 size:=size−1。
返回 true \text{true} true。
对于获取队首元素和获取队尾元素操作,首先判断循环双端队列是否为空,如果循环双端队列为空则返回 − 1 -1 −1,如果循环双端队列不为空则返回对应结点的值。
对于获取队首元素操作,返回 head . value \textit{head}.\textit{value} head.value。
对于获取队尾元素操作,返回 tail . value \textit{tail}.\textit{value} tail.value。
对于检查循环双端队列是否为空和检查循环双端队列是否已满操作,需要根据循环双端队列中的元素个数判断。
当且仅当 size = 0 \textit{size} = 0 size=0 时,循环双端队列为空。
当且仅当 size = capacity \textit{size} = \textit{capacity} size=capacity 时,循环双端队列已满。
检查循环双端队列是否为空和检查循环双端队列是否已满操作可以在其余的操作中复用。
class MyCircularDeque {
Node head;
Node tail;
int capacity;
int size;
public MyCircularDeque(int k) {
head = null;
tail = null;
capacity = k;
size = 0;
}
public boolean insertFront(int value) {
if (isFull()) {
return false;
}
Node node = new Node(value);
if (isEmpty()) {
head = tail = node;
} else {
head.prev = node;
node.next = head;
head = node;
}
head.prev = tail;
tail.next = head;
size++;
return true;
}
public boolean insertLast(int value) {
if (isFull()) {
return false;
}
Node node = new Node(value);
if (isEmpty()) {
head = tail = node;
} else {
tail.next = node;
node.prev = tail;
tail = node;
}
head.prev = tail;
tail.next = head;
size++;
return true;
}
public boolean deleteFront() {
if (isEmpty()) {
return false;
}
head = head.next;
head.prev = tail;
tail.next = head;
size--;
return true;
}
public boolean deleteLast() {
if (isEmpty()) {
return false;
}
tail = tail.prev;
head.prev = tail;
tail.next = head;
size--;
return true;
}
public int getFront() {
if (isEmpty()) {
return -1;
}
return head.value;
}
public int getRear() {
if (isEmpty()) {
return -1;
}
return tail.value;
}
public boolean isEmpty() {
return size == 0;
}
public boolean isFull() {
return size == capacity;
}
}
class Node {
int value;
Node prev;
Node next;
public Node(int value) {
this.value = value;
}
}
时间复杂度:构造方法和每一项操作的时间复杂度是 O ( 1 ) O(1) O(1)。
空间复杂度: O ( k ) O(k) O(k),其中 k k k 是循环双端队列的大小。