循环队列实现(携程)

71 阅读3分钟

题目要求

leetcode.cn/problems/de…

设计你的循环队列实现。 循环队列是一种线性数据结构,其操作表现基于 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 的范围内;
请不要使用内置的队列库。

参考题解

循环队列的核心在于如何判断队列是空还是满,以及如何实现循环的逻辑。我们可以使用以下策略:

  1. 使用一个数组作为底层数据存储
  2. 使用两个指针指向队头和队尾,
  3. 插入元素在队尾,队尾指针移动到数组最后一个位置时,下一步重新指向数组第一个位置,覆盖原来队头的值,从而实现空间上的复用
  4. 删除元素在队头,只需将队头指针向前移动一步,即可认为原队头被丢弃了
  5. 为了区分队列空和队列满的情况,使用字段 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
}