Leetcode622:题目
设计一个可以容纳 k 个元素的循环队列。需要实现以下接口:
class MyCircularQueue {
// 参数k表示这个循环队列最多只能容纳k个元素
public MyCircularQueue(int k);
// 将value放到队列中, 成功返回true
public boolean enQueue(int value);
// 删除队首元素,成功返回true
public boolean deQueue();
// 得到队首元素,如果为空,返回-1
public int Front();
// 得到队尾元素,如果队列为空,返回-1
public int Rear();
// 看一下循环队列是否为空
public boolean isEmpty();
// 看一下循环队列是否已放满k个元素
public boolean isFull();
}
解法一
我们使用front, rear和used来表示循环队列的头指针、尾指针和队列中已存在的元素数量
图中的k为6
注意:
1、我们可以看到,当队列为空或者队列满时,front和rear指向的区域相同;只有当队列不为空并且未满时,front和rear才指向不同的区域,其中rear指向尾部元素的后一块空间。
2、由于front和rear存在指向一块空间的情况,因此需要使用used变量来表示当前循环队列是否为空或是否为满。
3、在面试时只能通过普通的数组结构进行,因此需要规定好下标的活动范围: [0, k - 1]
4、index=i时,前一个元素是i-1, 后一个元素是i+1
5、index=0时,前一个元素是capacity-1,后一个元素是1
6、index=capacity-1时,前一个元素是capacity-2,后一个元素是0
以上前后元素下标的计算方式都可以用如下公式计算:
前一个元素下标:(index - 1 + capacity) % capacity
后一个元素下标:(index + 1) % capacity
根据上述分析可以得到如下代码:
class MyCircularQueue {
// 队首指针
private int front = 0;
// 队尾指针: 指向队尾元素的下一块区域
private int rear = 0;
// 队列中已经存在的元素个数
private int used = 0;
// 队列容量
private int capacity = 0;
// 队列中已经存在的元素(数组)
private int[] nums = null;
// 初始化
public MyCircularQueue(int k) {
this.capacity = k;
nums = new int[k];
}
public boolean enQueue(int value) {
// 如果队列已满,则无法插入元素
if (isFull()) {
return false;
}
// 添加元素并将队尾指针指向下一块区域
nums[rear] = value;
rear = (rear + 1) % capacity;
used += 1;
return true;
}
public boolean deQueue() {
if (isEmpty()) {
return false;
}
// 提取队首元素,将指针指向下一个元素所在区域
int ret = nums[front];
front = (front + 1) % capacity;
used--;
return true;
}
public int Front() {
if (isEmpty()) {
return -1;
}
return nums[front];
}
public int Rear() {
if (isEmpty()) {
return -1;
}
// 由于rear指向的是队尾元素的下一块区域,所以获取队尾元素需要进行 -1后取模
int tail = (rear - 1 + capacity) % capacity;
return nums[tail];
}
public boolean isEmpty() {
if (used == 0) {
return true;
}
return false;
}
public boolean isFull() {
if (used == capacity) {
return true;
}
return false;
}
}
解法二
解法一中使用的是容量为k的情况,还可以使用k + 1容量的队列存储k个元素:
1、申请空间时,需要申请k+1大小的数组(实际存储k个元素)
2、当元素满时,front和rear需要保证有间隙
可以发现,
1、循环队列实际上浪费了一个元素的空间,front和rear的下标区间为[0, k]。这个浪费的元素必须卡在 front 与 rear 之间。判断队列空或者满可以:
front == rear, 此时队列为空;
(rear + 1) % capacity == front,此时队列为满。
2、由于空队列和满队列front和rear下标的指向不同,所以可以不需要用used来判断队列是否为满
class MyCircularQueue {
// 队首指针
private int front = 0;
// 队尾指针: 指向队尾元素的下一块区域
private int rear = 0;
// 队列容量
private int capacity = 0;
// 队列中已经存在的元素(数组)
private int[] nums = null;
// 初始化
public MyCircularQueue(int k) {
this.capacity = k + 1;
nums = new int[k + 1];
}
public boolean enQueue(int value) {
// 如果队列已满,则无法插入元素
if (isFull()) {
return false;
}
// 添加元素并将队尾指针指向下一块区域
nums[rear] = value;
rear = (rear + 1) % capacity;
return true;
}
public boolean deQueue() {
if (isEmpty()) {
return false;
}
// 提取队首元素,将指针指向下一个元素所在区域
int ret = nums[front];
front = (front + 1) % capacity;
return true;
}
public int Front() {
if (isEmpty()) {
return -1;
}
return nums[front];
}
public int Rear() {
if (isEmpty()) {
return -1;
}
// 由于rear指向的是队尾元素的下一块区域,所以获取队尾元素需要进行 -1后取模
int tail = (rear - 1 + capacity) % capacity;
return nums[tail];
}
public boolean isEmpty() {
if (front == rear) {
return true;
}
return false;
}
public boolean isFull() {
// 队列满时,头指针和尾指针相邻
if ((rear + 1) % capacity == front) {
return true;
}
return false;
}
}
总结
相似点
两种解法都使用了取模的方法;当求前一个元素下标时,一定要使用(front - 1 + capacity) % capacity, 否则当front为0时会出错
不同点
解法一使用了三种控制变量;解法二使用了两种控制变量,但是损失了一个元素的存储空间。在多线程编程的情况下,控制变量越少,越容易实现无锁编程;因此使用解法二更容易实现无锁队列