LeetCode 622. 设计循环队列

LeetCode :https://leetcode-cn.com/problems/design-circular-queue/

题目描述:设计你的循环队列实现。

循环队列是一种线性数据结构,其操作表现基于 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

提示:

所有的值都在 0 至 1000 的范围内;
操作数将在 1 至 1000 的范围内;
请不要使用内置的队列库。

 

解题思路:

队列的存储结构中使用的最多的是循环队列。

循环队列包括两个指针, front 指针指向队头元素, rear 指针指向队尾元素的下一个位置

队列为空的判断条件是:front == rear

队列满的判断条件是:(rear+1) % maxsize == front

队列长度的计算公式:(rear-front+maxsize) % maxsize

正常情况下,当 front == rear 是队列有可能是满也有可能是空,为了区分这两种情况,我们需要在front前添加一个闲置单元。

 

 

1、Java 代码实现

用一个数组array来实现,成员变量还有4个整形变量:

1)size 用来记录环形队列的最大长度

2)front 用来记录当前队首位置

3)rear 用来记录当前队尾位置

4)count 用来记录队列中当前有多少个元素

往环形队列中插入元素是往队尾的后一个位置插入元素,当队尾在数组的最右边时要插入元素的话,就在数组的最左边插入元素,之后队尾也就在数组的最左边了。在环形队列中删除元素是删除队首的元素,随着不断地删除元素,队首会从数组的最左边一直到最右边,然后又回到最左边。将队首的位置移动之后,就完成了删除的操作,原来位置的值不用修改。返回队首元素就是返回front位置的值。返回队尾元素就是返回rear位置的值。判断队列的空和满就是判断count的值是多大就可以了。

class MyCircularQueue {
    int[] array;
    int size = 0;       // 队列总大小, size = k
    int count = 0;      // 队列元素个数, 默认 0
    int front = 0;      // 队列头的下标, 默认 0
    int rear = 0;       // 队列尾的下标, 默认 0

    public MyCircularQueue(int k) {
        array = new int[k];     // array.length = size = k
        size = k;
        count = 0;
        front = 0;
        rear = 0;
    }
    
    public boolean enQueue(int value) {
        if(isFull()) return false;  // 队列满了, 就不再存了

        array[rear] = value;
        rear = (rear + 1) % size;   // 队列尾移到下一个下标位置
        count++;
        return true;
    }
    
    public boolean deQueue() {
        if(isEmpty()) return false;     // 空队列, 没元素出队

        front = (front + 1) % size;     // 队头加1, 队首元素并没删
        count--;    // 元素个数减1, 表示少了1个元素, 实际并没删除队首元素
        return true;
    }
    
    public int Front() {
        if(isEmpty()) return -1;
        return array[front];
    }
    
    public int Rear() {
        if(isEmpty()) return -1;
        // return array[(size+rear-1)%size];   // 队尾总是指向下一个下标 Success
        if(rear == 0) return array[size-1];
        return array[rear-1];
    }
    
    public boolean isEmpty() {
        return count == 0;
    }
    
    public boolean isFull() {
        return count == size;
    }
}

/**
 * Your MyCircularQueue object will be instantiated and called as such:
 * MyCircularQueue obj = new MyCircularQueue(k);
 * boolean param_1 = obj.enQueue(value);
 * boolean param_2 = obj.deQueue();
 * int param_3 = obj.Front();
 * int param_4 = obj.Rear();
 * boolean param_5 = obj.isEmpty();
 * boolean param_6 = obj.isFull();
 */

 

2、Java 改进版删除了队列大小 size

class MyCircularQueue {
    int[] array;
    int count = 0;      // 队列元素个数, 默认 0
    int front = 0;      // 队列头的下标, 默认 0
    int rear = 0;       // 队列尾的下标, 默认 0

    public MyCircularQueue(int k) {
        array = new int[k];     // array.length = k
        count = 0;
        front = 0;
        rear = 0;
    }
    
    public boolean enQueue(int value) {
        if(isFull()) return false;              // 队列满了, 就不再存了

        array[rear] = value;
        rear = (rear + 1) % array.length;       // 队列尾移到下一个下标位置
        count++;
        return true;
    }
    
    public boolean deQueue() {
        if(isEmpty()) return false;             // 空队列, 没元素出队

        front = (front + 1) % array.length;     // 队头加1, 队首元素并没删
        count--;    // 元素个数减1, 表示少了1个元素, 实际并没删除队首元素
        return true;
    }
    
    public int Front() {
        if(isEmpty()) return -1;
        return array[front];
    }
    
    public int Rear() {
        if(isEmpty()) return -1;
        // return array[(array.length+rear-1)%array.length];   // 队尾总是指向下一个下标 Success
        return rear == 0 ? array[array.length-1] : array[rear-1];
    }
    
    public boolean isEmpty() {
        return count == 0;
    }
    
    public boolean isFull() {
        return count == array.length;
    }
}

/**
 * Your MyCircularQueue object will be instantiated and called as such:
 * MyCircularQueue obj = new MyCircularQueue(k);
 * boolean param_1 = obj.enQueue(value);
 * boolean param_2 = obj.deQueue();
 * int param_3 = obj.Front();
 * int param_4 = obj.Rear();
 * boolean param_5 = obj.isEmpty();
 * boolean param_6 = obj.isFull();
 */

 

3、Python 代码实现

class MyCircularQueue(object):

    def __init__(self, k):
        """
        :type k: int
        """
        self.array = [None] * (k+1)     // 循环队列多加了一个间隔元素
        self.maxSize = k + 1
        self.front = 0
        self.rear = 0


    def enQueue(self, value):
        """
        :type value: int
        :rtype: bool
        """
        if self.isFull(): return False
        self.array[self.rear] = value
        self.rear = (self.rear + 1) % self.maxSize
        return True

    def deQueue(self):
        """
        :rtype: bool
        """
        if self.isEmpty(): return False
        self.front = (self.front + 1) % self.maxSize
        return True

    def Front(self):
        """
        :rtype: int
        """
        if self.isEmpty(): return -1
        return self.array[self.front]

    def Rear(self):
        """
        :rtype: int
        """
        if self.isEmpty(): return -1
        return self.array[(self.rear-1+self.maxSize) % self.maxSize]

    def isEmpty(self):
        """
        :rtype: bool
        """
        return self.front == self.rear

    def isFull(self):
        """
        :rtype: bool
        """
        return self.front == ((self.rear + 1) % self.maxSize)


# Your MyCircularQueue object will be instantiated and called as such:
# obj = MyCircularQueue(k)
# param_1 = obj.enQueue(value)
# param_2 = obj.deQueue()
# param_3 = obj.Front()
# param_4 = obj.Rear()
# param_5 = obj.isEmpty()
# param_6 = obj.isFull()

 

4、Java代码改进循环队列满了后,依然可以放入元素,删除元素的实现

/*
* 实现一个循环队列,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。
* 应支持如下操作:
* 1、MyCircularQueue(k): 构造器,设置队列长度为 k 。
* 2、Front: 从队首获取元素。如果队列为空,返回 -1 。
* 3、Rear: 获取队尾元素。如果队列为空,返回 -1 。
* 4、enQueue(value): 向循环队列插入一个元素。如果成功插入则返回真。
* 5、deQueue(): 从循环队列中删除一个元素。如果成功删除则返回真。 
* 6、isEmpty(): 检查循环队列是否为空。
* 7、isFull(): 检查循环队列是否已满。
* 
* 2021.02.02  in tencent.com
*/


// Java	 
class MyCircularQueue {
	
	// FIFO

	// array = []			// 队列为空
	// array = [1,2,3,,]	// 队列未满 
	// array = [1,2,3,4,5]	// 队列满了
	// array = [6,7,3,4,5]	// 满了还插入
	// array = [6,7,,1,2]	// 满了出队列


	int[] array;
	int SIZE = 0;
	int count = 0;
	int front = 0;
	int rear = 0;
	
	public  MyCircularQueue(int k) {
		array = new int[k];
		SIZE = k;		// 记录队列的总大小
		count = 0;		// 记录队列里的元素个数, 队列满了则 count = k, 出队列则 count = count-1
		front = 0;
		rear = 0;
	}


	// 插入实现1: 循环队列满了后, 再次插入新元素, 直接存入front位置
	public boolean enQueue(int value) {
		if(count < SIZE) {		// 队列未满
			array[rear] = value;
			count++;			// 队列未满时, count一直累加
		}

		if(front == rear) {		// 队列已满
			array[front] = value;
			front = (front + 1) % SIZE;
		} 

		rear = (rear + 1) % SIZE;	// 队列满了后, rear = front 实际指向同一个下标了(故不能通过 front==rear判断队列是空或满)
		return true;
	}


	// 插入实现2: 循环队列满了后, 再次插入新元素, 直接存入rear位置
	public boolean enQueue2(int value) {
		array[rear] = value;
		count++;	

		if(count >= SIZE) {		// 已满
			front = (rear + 1) % SIZE;
			count = SIZE;		// 队列满了后, count一直等于最大值SIZE
		} 
		
		rear = (rear + 1) % SIZE;	// 队列满了后, rear = front 实际指向同一个下标了(故不能通过 front==rear判断队列是空或满)
		return true;
	}

	// public boolean deQueue() {
		if(isEmpty()) return -1;

		front = (front + 1) % SIZE;
		count--;
		return true;
	}

	// public int Front() {
		if(isEmpty()) return -1;

		return array[front];
	}

	// public int Rear() {
		if(isEmpty()) return -1;

		// return rear == 0 ? array[SIZE-1] : array[rear-1];	// Success

		rear = (SIZE+rear-1) % SIZE;	// Success
		return array[rear]
	}

	public boolean isEmpty() {
		return count == 0;
	}

	public boolean isFull() {
		return count == SIZE;
	}
}

注意点:

1)队列满了后,依然可以插入循环队列,enQueue() 和 enQueue2() 实现

2)判断队列是否满了,有两个方式:

     方式一:引入一个计数器 count,每次入队列、出队列,都计数count,判断 count == 0 或 count == SIZE

     方式二:队列多加一个空元素,隔离队首和队尾,当 (rear + 1) % SIZE == front,则表示队列满了

 

5、思考题

上面的循环队列,改成 "约瑟夫环",又该如何实现呢?

 

 

参考推荐:

八大排序算法图文讲解

LeetCode 622. 设计循环队列

经典排序算法的复杂度分析

10大程序员基础实用算法

面试最常用的10大算法

KMP 字符串匹配算法

哈希表算法原理

ZeroMQ 异步消息队列通信

Python学习入门(29)——消息队列

Python 常用加密算法 base64, md5, sha1

FIFO、LRU、LFU 缓存淘汰算法的原理

Bloom Filter 算法处理海量数据

SimHash 算法原理及实现

Java 实现线程间通知和唤醒

HTTP 协议的历史进化演变

区块链核心算法与技术原理

聚类算法之KMeans(Java实现)

程序猿追MM的各种算法