641. 设计循环双端队列

185 阅读4分钟

题目描述

设计实现双端队列。 你的实现需要支持以下操作:

MyCircularDeque(k):构造函数,双端队列的大小为k。
insertFront():将一个元素添加到双端队列头部。 如果操作成功返回 true。
insertLast():将一个元素添加到双端队列尾部。如果操作成功返回 true。
deleteFront():从双端队列头部删除一个元素。 如果操作成功返回 true。
deleteLast():从双端队列尾部删除一个元素。如果操作成功返回 true。
getFront():从双端队列头部获得一个元素。如果双端队列为空,返回 -1。
getRear():获得双端队列的最后一个元素。 如果双端队列为空,返回 -1。
isEmpty():检查双端队列是否为空。
isFull():检查双端队列是否满了。

分析

输入:队列的最大长度 k 输出:队列的实例,有题目要求的功能

解题思路

这题要我们写一个构造函数,然后返回的实例会有一些方法,所以我们需要关注的是两部分:

  1. 数据结构的属性
  2. 方法的实现

数据结构的属性

如果一个数据结构使用的属性少,那么维护起来就会很方便,但是这样做会使得每次需要其他的属性的时候,比如只定义了队列第一个元素所在的 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 直接相关