前端算法小白攻略12-leetcode(设计循环队列+双端+前中后)

197 阅读6分钟

前言

链表相关的题暂时先告一段落(后面会复习),今天我们进入队列和栈的相关算法题,先从队列开始

队列的定义:

对列: 是一种特殊的线性表,只能从队首删除元素,队尾插入元素,遵循FIFO先入先出的原则; 在JavaScript种我们可以用数组来模拟队列
循环队列: 是队列的升级版,队尾连接在队首之后,形成一个闭环,那么只有队尾后有剩余空间我们就可以使用,而普通队列在队尾指针走到队列的末端就插入不了元素了

题目描述

622. 设计循环队列

设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为“环形缓冲器”。

循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。

你的实现应该支持如下操作:

MyCircularQueue(k): 构造器,设置队列长度为 k 。
Front: 从队首获取元素。如果队列为空,返回 -1 。
Rear: 获取队尾元素。如果队列为空,返回 -1 。
enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。
isEmpty(): 检查循环队列是否为空。
isFull(): 检查循环队列是否已满。  

示例:

MyCircularQueue circularQueue = new MyCircularQueue(3); // 设置长度为 3
circularQueue.enQueue(1);  // 返回 true
circularQueue.enQueue(2);  // 返回 true
circularQueue.enQueue(3);  // 返回 true
circularQueue.enQueue(4);  // 返回 false,队列已满
circularQueue.Rear();  // 返回 3
circularQueue.isFull();  // 返回 true
circularQueue.deQueue();  // 返回 true
circularQueue.enQueue(4);  // 返回 true
circularQueue.Rear();  // 返回 4

解题思路

  1. 首先得建个数组来模拟队列,对首指针front指向队列第一个元素队尾rear指针指向最后一个元素的下一位
  2. 那么数组长度弄多少呢? 用队列长度k+1,但是数组最大填充长度是max = k,原因见下条
  3. 队列判满: (rear-front+max+1)%(max+1) 的值为max=k
    例如: max=k=4 队列长队则为5,元素最大下标4,最小为0:
    rear=4,front=0队列填满(rear-front+5)%5=4;
    rear=0,front=1队列填满(0-1+5)%5=4;
    取余加max+1的原因(懂了就这题就拿捏了) 为了避免上面rear-front出现负数的情况,加上循环队里的长度再取余即得所占队列长度,rear-front正数结果就是占用队列长度再加上一个必定大于他的max+1取余则还是原来的正数
  4. 队列判空: (rear-front+max+1)%(max+1) 值为 0
  5. 删除元素 队首指针前移1位,添加元素 直接队尾指针位置复制然后将队尾指针后移1位

开始解题

/**
 * @param {number} k
 */
var MyCircularQueue = function(k) {
   this.queue = new Array(k+1);
   this.front = 0;
   this.rear = 0;
   this.max = k;
};

/** 
 * @param {number} value
 * @return {boolean}
 */
MyCircularQueue.prototype.enQueue = function(value) {
    if(this.isFull()) return false;
    let cur = (this.rear+1) % (this.max+1);
    this.queue[this.rear] = value;
    this.rear = cur;
    return true;
};

/**
 * @return {boolean}
 */
MyCircularQueue.prototype.deQueue = function() {
    if(this.isEmpty()) return false;
    this.front = (this.front+1) % (this.max + 1);  // 加this.max+1就是为了循环起来,走到底+1=this.max+1刚好取余归0重新开始
    return true;
};

/**
 * @return {number}
 */
MyCircularQueue.prototype.Front = function() {
    if(this.isEmpty()) return -1;
    return this.queue[this.front];
};

/**
 * @return {number}
 */
MyCircularQueue.prototype.Rear = function() { // this.rear指针指向尾元素后一位
    if(this.isEmpty()) return -1;
    return this.queue[(this.rear+this.max) % (this.max+1)] // 原代码 (this.rear-1+this.max+1)%(this.max+1)
};

/**
 * @return {boolean}
 */
MyCircularQueue.prototype.isEmpty = function() {
    return (this.rear-this.front+this.max+1) % (this.max + 1) === 0;
};

/**
 * @return {boolean}
 */
MyCircularQueue.prototype.isFull = function() {
    return (this.rear-this.front+this.max+1) % (this.max + 1) === this.max;
};

641. 设计循环双端队列

解题思路:

  1. 循环队列是后入前出的FIFO,其实双端循环队列就是前后均可出入队列,多了前入后出,在循环队列的解题逻辑加上即可

添加前入后出代码:

MyCircularQueue.insertFront = function(value){ // 对首入队,头指针前移1位
    if(this.isFull()) return false;
    let temp = (this.front-1 + this.max +1) % (this.max+1); // -1+1可省略这里为了好理解
    this.queue[temp] = value;  
    this.front = temp; 
    return true;
}
MyCircularQueue.deleteLast = function(){ // 对尾出队,尾指针前移1位
    if(this.isEmpty()) return false;
    this.rear = (this.rear-1+this.max+1) % (this.max+1);
    return true;
}

1670. 设计前中后队列

请你设计一个队列,支持在前,中,后三个位置的 push 和 pop 操作。

请你完成 FrontMiddleBack 类:

FrontMiddleBack() 初始化队列。
void pushFront(int val) 将 val 添加到队列的 最前面 。
void pushMiddle(int val) 将 val 添加到队列的 正中间 。
void pushBack(int val) 将 val 添加到队里的 最后面 。
int popFront() 将 最前面 的元素从队列中删除并返回值,如果删除之前队列为空,那么返回 -1 。
int popMiddle() 将 正中间 的元素从队列中删除并返回值,如果删除之前队列为空,那么返回 -1 。
int popBack() 将 最后面 的元素从队列中删除并返回值,如果删除之前队列为空,那么返回 -1 。
请注意当有 两个 中间位置的时候,选择靠前面的位置进行操作。比方说:

将 6 添加到 [1, 2, 3, 4, 5] 的中间位置,结果数组为 [1, 2, 6, 3, 4, 5] 。
从 [1, 2, 3, 4, 5, 6] 的中间位置弹出元素返回 3 ,数组变为 [1, 2, 4, 5, 6] 。

解题思路:

  1. 上方红色区域为重点思路体现,全局围绕此操作,决定中间的出入队操作

  2. 因为一个数组我们不好判断正中间的位置,所以需要用到左右两个数组来模拟前中后队列

  3. 如何判断正中间的操作?

    队列为奇数长度时:
    [1, 2, 3, 4, 5]在中间值的左边即小于数组长度一半的位置增加了6 =》[1, 2, 6, 3, 4, 5]
    队列为偶数长度时:
    [1,2,3,4,5,6]弹出的是靠左边中间值的3,让左边一半小于原数组长度 =》[1,2,4,5,6]

    由此可得,在队列操作过程中,左边队列属组的长度一定是小于或等于右边数组的长度的,那么我们在编写代码的时候围绕这一点进行即可。

  4. 由题目描述本题我们只需对队列进行判空操作

  5. 队首和队尾出入队我们先执行出入队操作再平衡左右数组长度,左边数组头节点为队首,右边数组尾节点为队尾

  6. 队中间出入队需要先判断左右数组的长度,分为两种情况考虑:左边小于右边和左边等于右边

开始解题

var FrontMiddleBackQueue = function() {
    this.leftQ = [];
    this.rightQ = [];
};

/** 
 * @param {number} val
 * @return {void}
 */
FrontMiddleBackQueue.prototype.pushFront = function(val) {
    this.leftQ.unshift(val);                     // 队首先入队
    if(this.leftQ.length > this.rightQ.length) { // 左边大于时,左边队尾出队一个添加到右边的队首
        this.rightQ.unshift(this.leftQ.pop())
    }
};

/** 
 * @param {number} val
 * @return {void}
 */
FrontMiddleBackQueue.prototype.pushMiddle = function(val) {
    if(this.rightQ.length == this.leftQ.length) { // 队中间入队当左右数组长度相等时,添加到右边队首
        this.rightQ.unshift(val);
    }else {                                       // 否则添加到左边队尾
        this.leftQ.push(val);
    }
};

/** 
 * @param {number} val
 * @return {void}
 */
FrontMiddleBackQueue.prototype.pushBack = function(val) {
    this.rightQ.push(val);
    if(this.leftQ.length+2 == this.rightQ.length) { // 当右边数组长度等于比左边大2时,右边队首出队一个添加到左边队尾
        this.leftQ.push(this.rightQ.shift());
    }
};

/**
 * @return {number}
 */
FrontMiddleBackQueue.prototype.popFront = function() {
    if(this.rightQ.length === 0) return -1;  // 因为右边数组长度始终大于等于左边,所以判空只需右边为0
    if(!this.leftQ.length) return this.rightQ.pop(); // 当左边数组长度为0时,直接让右边数组出队
    let popVal = this.leftQ.shift();
    if(this.leftQ.length+1 < this.rightQ.length){
        this.leftQ.push(this.rightQ.shift());
    }
    return popVal;
};

/**
 * @return {number}
 */
FrontMiddleBackQueue.prototype.popMiddle = function() {
    if(this.rightQ.length === 0) return -1;
    let popVal;
    if(this.leftQ.length < this.rightQ.length) {
        popVal = this.rightQ.shift();
    }else {
        popVal = this.leftQ.pop();
    }
    return popVal;
};

/**
 * @return {number}
 */
FrontMiddleBackQueue.prototype.popBack = function() {
    if(this.rightQ.length === 0) return -1;
    let popVal = this.rightQ.pop();
    if(this.rightQ.length < this.leftQ.length) {
        this.rightQ.unshift(this.leftQ.pop());
    }
    return popVal;
};