题目
标题和出处
标题:设计循环双端队列
难度
6 级
题目描述
要求
设计你的循环双端队列实现。
实现 MyCircularDeque \texttt{MyCircularDeque} MyCircularDeque 类:
- MyCircularDeque(k) \texttt{MyCircularDeque(k)} MyCircularDeque(k) 初始化对象,将循环双端队列的大小设为 k \texttt{k} k。
- boolean insertFront(int value) \texttt{boolean insertFront(int value)} boolean insertFront(int value) 向循环双端队列的队首插入一个元素。如果操作成功则返回 true \texttt{true} true,否则返回 false \texttt{false} false。
- boolean insertLast(int value) \texttt{boolean insertLast(int value)} boolean insertLast(int value) 向循环双端队列的队尾插入一个元素。如果操作成功则返回 true \texttt{true} true,否则返回 false \texttt{false} false。
- boolean deleteFront() \texttt{boolean deleteFront()} boolean deleteFront() 从循环双端队列的队首删除一个元素。如果操作成功则返回 true \texttt{true} true,否则返回 false \texttt{false} false。
- boolean deleteLast() \texttt{boolean deleteLast()} boolean deleteLast() 从循环双端队列的队尾删除一个元素。如果操作成功则返回 true \texttt{true} true,否则返回 false \texttt{false} false。
- int getFront() \texttt{int getFront()} int getFront() 获取队首元素。如果双端队列为空,返回 -1 \texttt{-1} -1。
- int getRear() \texttt{int getRear()} int getRear() 获取队尾元素。如果双端队列为空,返回 -1 \texttt{-1} -1。
- boolean isEmpty() \texttt{boolean isEmpty()} boolean isEmpty() 如果循环双端队列为空则返回 true \texttt{true} true,否则返回 false \texttt{false} false。
- boolean isFull() \texttt{boolean isFull()} boolean isFull() 如果循环双端队列已满则返回 true \texttt{true} true,否则返回 false \texttt{false} false。
你不允许使用内置的队列数据结构。
示例
示例 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
数据范围
- 1 ≤ k ≤ 1000 \texttt{1} \le \texttt{k} \le \texttt{1000} 1≤k≤1000
- 0 ≤ value ≤ 1000 \texttt{0} \le \texttt{value} \le \texttt{1000} 0≤value≤1000
- 最多调用 3000 \texttt{3000} 3000 次 insertFront \texttt{insertFront} insertFront、 insertLast \texttt{insertLast} insertLast、 deleteFront \texttt{deleteFront} deleteFront、 deleteLast \texttt{deleteLast} deleteLast、 getFront \texttt{getFront} getFront、 getRear \texttt{getRear} getRear、 isEmpty \texttt{isEmpty} isEmpty 和 isFull \texttt{isFull} isFull
前言
这道题和「设计循环队列」相似,区别在于这道题需要设计的结构是双端队列,双端队列支持在队首和队尾添加元素和删除元素。
循环双端队列的实现有两种方式,分别是基于数组和基于双向链表。数组和双向链表本身并不是环形结构,在实现时需要模拟环形结构。
解法一
思路和算法
使用数组实现循环双端队列时,需要维护数组、循环双端队列的大小(即最多允许存储的元素个数)、队首下标、队尾下标和循环双端队列中的元素个数。其中,数组的长度为循环双端队列的大小,队首下标表示队首元素所在位置的下标,队尾下标表示队尾元素的后一个位置的下标。
构造方法中,将数组 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 是循环双端队列的大小。