题目描述
设计实现双端队列。 你的实现需要支持以下操作:
MyCircularDeque(k):构造函数,双端队列的大小为k。
insertFront():将一个元素添加到双端队列头部。 如果操作成功返回 true。
insertLast():将一个元素添加到双端队列尾部。如果操作成功返回 true。
deleteFront():从双端队列头部删除一个元素。 如果操作成功返回 true。
deleteLast():从双端队列尾部删除一个元素。如果操作成功返回 true。
getFront():从双端队列头部获得一个元素。如果双端队列为空,返回 -1。
getRear():获得双端队列的最后一个元素。 如果双端队列为空,返回 -1。
isEmpty():检查双端队列是否为空。
isFull():检查双端队列是否满了。
分析
输入:队列的最大长度 k
输出:队列的实例,有题目要求的功能
解题思路
这题要我们写一个构造函数,然后返回的实例会有一些方法,所以我们需要关注的是两部分:
- 数据结构的属性
- 方法的实现
数据结构的属性
如果一个数据结构使用的属性少,那么维护起来就会很方便,但是这样做会使得每次需要其他的属性的时候,比如只定义了队列第一个元素所在的 headIndex,队列当前长度 count,那么我在每次需要使用 tailIndex 的时候,都需要计算,这部分就需要占用一定的时间复杂度。
如果定义了一堆属性,那我们在进行方法实现的时候,需要考虑到所有属性的变化,需要考虑的会很多,很容易出现错误。
所以这是一个平衡的点。
与定义非双端的队列一样,我们这里定义:
headIndex,作为队列首个元素所在 index,
queue,我们用这个数组来存储数据
count,表示当前的队列长度
capacity,队列的最长长度
方法实现
insertFront:
边界条件:如果已经满了,不再 insert
要向头部插入一个元素,我们需要考虑两部分:
插入的位置,headIndex 的移动。
很显然,我们要往 headIndex 移动后的位置去插入元素,所以应当先移动 headIndex,再进行插入操作。
通常情况下,我们只需要把 headIndex 往前移动一个位置就可以了,
但是也要考虑到位置为 0 的情况,这时候实际上是往 tail 的位置插入。
对于这种特殊情况,如果把他和一般情况综合起来考虑的话,就需要把 headIndex 走的长度队列的 capacity 相结合考虑。因此我们需要让 headIndex - 1 绕一周让他去到达指定的地点,也就是模 capacity。最后还需要增加 count。
insertLast:
边界条件:如果队列已经满了,不再 insert
也需要考虑插入位置,但是因为 tail 的位置是计算出来的,我们只需要算出来他的位置,插入 value 就可以了。
对于 tail 的位置,需要让 headIndex 走队列当前的长度 count,然后像 insertFront 那里一样,模 capacity 去处理超出边界的情况。
deleteFront:
边界条件:如果队列为空,不再删除
这里我的处理并不是让数组删除一个值,只是把 headIndex 的指向向前移动一位,如果有新的值进来,覆盖老的就可以了。
由于需要考虑 headIndex 位于数组尾部的情况,我们仍然需要让他模 capacity。
deleteLast:
边界条件:如果队列为空,不再删除
这边我的操作是直接让 count 减1⃣️,是个比较巧妙的操作。
因为我们在删除,插入的时候,都会用到 count,所以直接操作他,插入删除的位置也会改变~
其余方法和非双端的队列一致,不再赘述~
代码
/**
* @param {number} k
*/
var MyCircularDeque = function (k) {
this.capacity = k;
this.headIndex = 0;
this.queue = [];
this.count = 0;
};
/**
* @param {number} value
* @return {boolean}
*/
MyCircularDeque.prototype.insertFront = function (value) {
if (this.isFull()) return false;
this.headIndex = (this.headIndex - 1 + this.capacity) % this.capacity;
this.queue[headIndex] = value;
this.count++;
return true;
};
/**
* @param {number} value
* @return {boolean}
*/
MyCircularDeque.prototype.insertLast = function (value) {
if (this.isFull()) return false;
const tailIndex = (this.headIndex + this.count) % this.capacity;
this.queue[tailIndex] = value;
this.count++;
return true;
};
/**
* @return {boolean}
*/
MyCircularDeque.prototype.deleteFront = function () {
if (this.isEmpty()) return false;
this.headIndex = (this.headIndex + 1) % this.capacity;
this.count--;
return true;
};
/**
* @return {boolean}
*/
MyCircularDeque.prototype.deleteLast = function () {
if (this.isEmpty()) return false;
this.count--;
return true;
};
/**
* @return {number}
*/
MyCircularDeque.prototype.getFront = function () {
if (this.isEmpty()) return -1;
return this.queue[this.headIndex];
};
/**
* @return {number}
*/
MyCircularDeque.prototype.getRear = function () {
if (this.isEmpty()) return -1;
return this.queue[(this.headIndex + this.count - 1) % this.capacity];
};
/**
* @return {boolean}
*/
MyCircularDeque.prototype.isEmpty = function () {
return !this.count;
};
/**
* @return {boolean}
*/
MyCircularDeque.prototype.isFull = function () {
if (this.count === this.capacity) return true;
return false;
};
复杂度
时间:O(N),和队列的 capacity 有关,插入和删除的操作最坏会遍历一遍
空间:O(N),需要存储数据的量依然和 capacity 直接相关