• 队列题目:设计循环双端队列


    题目

    标题和出处

    标题:设计循环双端队列

    出处:641. 设计循环双端队列

    难度

    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} 1k1000
    • 0 ≤ value ≤ 1000 \texttt{0} \le \texttt{value} \le \texttt{1000} 0value1000
    • 最多调用 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,如果循环双端队列未满则执行在队首添加元素操作。

    1. head : = ( head + capacity − 1 )   m o d   capacity \textit{head} := (\textit{head} + \textit{capacity} - 1) \bmod \textit{capacity} head:=(head+capacity1)modcapacity,取余运算确保 head \textit{head} head 在下标范围内。

    2. deque [ head ] : = value \textit{deque}[\textit{head}] := \textit{value} deque[head]:=value

    3. size : = size + 1 \textit{size} := \textit{size} + 1 size:=size+1

    4. 返回 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,如果循环双端队列未满则执行在队尾添加元素操作。

    1. deque [ tail ] : = value \textit{deque}[\textit{tail}] := \textit{value} deque[tail]:=value

    2. tail : = ( tail + 1 )   m o d   capacity \textit{tail} := (\textit{tail} + 1) \bmod \textit{capacity} tail:=(tail+1)modcapacity,取余运算确保 tail \textit{tail} tail 在下标范围内。

    3. size : = size + 1 \textit{size} := \textit{size} + 1 size:=size+1

    4. 返回 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,如果循环双端队列不为空则执行在队首删除元素操作。

    1. head : = ( head + 1 )   m o d   capacity \textit{head} := (\textit{head} + 1) \bmod \textit{capacity} head:=(head+1)modcapacity,取余运算确保 head \textit{head} head 在下标范围内。

    2. size : = size − 1 \textit{size} := \textit{size} - 1 size:=size1

    3. 返回 true \text{true} true

    对于在队尾删除元素操作,首先判断循环双端队列是否为空,如果循环双端队列为空则返回 false \text{false} false,如果循环双端队列不为空则执行在队尾删除元素操作。

    1. tail : = ( tail + capacity − 1 )   m o d   capacity \textit{tail} := (\textit{tail} + \textit{capacity} - 1) \bmod \textit{capacity} tail:=(tail+capacity1)modcapacity,取余运算确保 tail \textit{tail} tail 在下标范围内。

    2. size : = size − 1 \textit{size} := \textit{size} - 1 size:=size1

    3. 返回 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+capacity1)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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75

    复杂度分析

    • 时间复杂度:构造方法和每一项操作的时间复杂度是 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,如果循环双端队列未满则执行在队首添加元素操作。

    1. 创建新结点 node \textit{node} node,新结点的值为 value \textit{value} value

    2. 如果循环双端队列为空,则令 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

    3. head . prev : = tail \textit{head}.\textit{prev} := \textit{tail} head.prev:=tail tail . next : = head \textit{tail}.\textit{next} := \textit{head} tail.next:=head,形成循环结构。

    4. size : = size + 1 \textit{size} := \textit{size} + 1 size:=size+1

    5. 返回 true \text{true} true

    对于在队尾添加元素操作,首先判断循环双端队列是否已满,如果循环双端队列已满则返回 false \text{false} false,如果循环双端队列未满则执行在队尾添加元素操作。

    1. 创建新结点 node \textit{node} node,新结点的值为 value \textit{value} value

    2. 如果循环双端队列为空,则令 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

    3. head . prev : = tail \textit{head}.\textit{prev} := \textit{tail} head.prev:=tail tail . next : = head \textit{tail}.\textit{next} := \textit{head} tail.next:=head,形成循环结构。

    4. size : = size + 1 \textit{size} := \textit{size} + 1 size:=size+1

    5. 返回 true \text{true} true

    对于在队首删除元素操作,首先判断循环双端队列是否为空,如果循环双端队列为空则返回 false \text{false} false,如果循环双端队列不为空则执行在队首删除元素操作。

    1. head : = head . next \textit{head} := \textit{head}.\textit{next} head:=head.next

    2. head . prev : = tail \textit{head}.\textit{prev} := \textit{tail} head.prev:=tail tail . next : = head \textit{tail}.\textit{next} := \textit{head} tail.next:=head,形成循环结构。

    3. size : = size − 1 \textit{size} := \textit{size} - 1 size:=size1

    4. 返回 true \text{true} true

    对于在队尾删除元素操作,首先判断循环双端队列是否为空,如果循环双端队列为空则返回 false \text{false} false,如果循环双端队列不为空则执行在队尾删除元素操作。

    1. tail : = tail . prev \textit{tail} := \textit{tail}.\textit{prev} tail:=tail.prev

    2. head . prev : = tail \textit{head}.\textit{prev} := \textit{tail} head.prev:=tail tail . next : = head \textit{tail}.\textit{next} := \textit{head} tail.next:=head,形成循环结构。

    3. size : = size − 1 \textit{size} := \textit{size} - 1 size:=size1

    4. 返回 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;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103

    复杂度分析

    • 时间复杂度:构造方法和每一项操作的时间复杂度是 O ( 1 ) O(1) O(1)

    • 空间复杂度: O ( k ) O(k) O(k),其中 k k k 是循环双端队列的大小。

  • 相关阅读:
    238.除自身以外数组的乘积
    java-php-python-ssm一起组局校园交友平台计算机毕业设计
    【VUE3】--创建vue-cli项目,setup+ref+reactive+vue2/vue3响应式原理【练习代码已上传至Gitee】
    纯CSS 自定义radio单选按扭、checkbox复选框 默认样式
    线性代数的本质(十)——矩阵分解
    Python入门【序列、列表简介、列表的创建 、列表元素的增加、列表元素的删除 】(四)-全面详解(学习总结---从入门到深化)
    数据结构-带头双向循环链表(增删查改)(2)
    C语言 编译和链接
    计算机操作系统(持续学习中)
    理解ELMo 模型
  • 原文地址:https://blog.csdn.net/stormsunshine/article/details/121069011