题目要求
设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 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 的范围内;
请不要使用内置的队列库。
参考题解
循环队列的核心在于如何判断队列是空还是满,以及如何实现循环的逻辑。我们可以使用以下策略:
- 使用一个数组作为底层数据存储
- 使用两个指针指向队头和队尾,
- 插入元素在队尾,队尾指针移动到数组最后一个位置时,下一步重新指向数组第一个位置,覆盖原来队头的值,从而实现空间上的复用
- 删除元素在队头,只需将队头指针向前移动一步,即可认为原队头被丢弃了
- 为了区分队列空和队列满的情况,使用字段
size和cap来记录队列中的元素个数和队列容量
func main() {
circularQueue := NewCircularQueue(3)
ok := circularQueue.enQueue(1)
fmt.Printf("add val 1: %v\n", ok)
ok = circularQueue.enQueue(2)
fmt.Printf("add val 2: %v\n", ok)
ok = circularQueue.enQueue(3)
fmt.Printf("add val 3: %v\n", ok)
ok = circularQueue.enQueue(4)
fmt.Printf("add val 4: %v\n", ok) // 1 -> 2 -> 3
fmt.Println("current front: ", circularQueue.Front())
fmt.Println("current rear: ", circularQueue.Rear())
fmt.Println("is Full: ", circularQueue.isFull())
ok = circularQueue.deQueue()
fmt.Printf("del front: %v\n", ok)
fmt.Println("current front: ", circularQueue.Front()) // 2 -> 3
ok = circularQueue.enQueue(4)
fmt.Printf("add val 4: %v\n", ok) // 2 -> 3 -> 4
fmt.Println("current rear: ", circularQueue.Rear())
}
type CircularQueue struct {
// 底层申请一个数组,头尾指针,环形遍历,记录元素个数
queue []int
head int
tail int
Size int // 记录环形数组中的元素个数,方便判空、判满
Cap int
}
func NewCircularQueue(k int) *CircularQueue {
return &CircularQueue{
Cap: k,
queue: make([]int, k),
head: 0,
tail: 0,
Size: 0,
}
}
func (this *CircularQueue) Front() int {
if this.isEmpty() {
return -1
}
return this.queue[this.head]
}
func (this *CircularQueue) Rear() int {
if this.isEmpty() {
return -1
}
// 每次插入元素,先放在tail指针指向位置,然后tail++
// 因此,队尾元素在tail-1的位置上
// 注意tail循环移动到队头的情况(即tail=0)
rearIdx := (this.tail - 1 + this.Cap) % this.Cap
return this.queue[rearIdx]
}
func (this *CircularQueue) enQueue(val int) bool {
// 判断容量是否满了
if this.isFull() {
return false
}
// 在队尾插入新元素
this.queue[this.tail] = val
// 更新tail指针,如果已经到了队列末尾,要重新指向头部
this.tail = (this.tail + 1) % this.Cap
this.Size++
return true
}
// FIFO,删除头部
func (this *CircularQueue) deQueue() bool {
if this.isEmpty() {
return false
}
// 更新队头指针
this.head = (this.head + 1) % this.Cap
this.Size--
return true
}
func (this *CircularQueue) isEmpty() bool {
if this.Size == 0 {
return true
}
return false
}
func (this *CircularQueue) isFull() bool {
if this.Size == this.Cap {
return true
}
return false
}