# 622. Design Circular Queue

## Problem Description

The problem requires designing a circular queue, which is a variation of a standard queue that allows for efficient utilization of storage space. In contrast with a conventional queue where once it becomes full you cannot add new elements, even if there is available space due to earlier removed elements, a circular queue allows you to use that space and continue adding new elements by circling back to the beginning of the queue.

The operations that need to be supported are:

• `MyCircularQueue(k)`: Constructor - initialize the queue with a fixed size `k`.
• `enQueue(value)`: Insert an element into the queue, return `true` if successful.
• `deQueue()`: Remove an element from the queue, return `true` if successful.
• `Front()`: Return the element at the front of the queue without removing it, return `-1` if the queue is empty.
• `Rear()`: Return the element at the end of the queue without removing it, return `-1` if the queue is empty.
• `isEmpty()`: Check if the queue is empty.
• `isFull()`: Check if the queue is full.

The implementation should not use any built-in queue data structures provided by the programming language.

## Intuition

In order to implement a circular queue, we can use a fixed-size array to represent the queue. Two pointers, `front` and `size`, and the `capacity` of the queue are needed to keep track of its state:

1. `front`: This stores the index of the first (front) element of the queue.
2. `size`: This represents the current number of elements in the queue.
3. `capacity`: This is the maximum number of elements our queue can hold, as defined upon initialization.

The key to achieving circular behavior is to use modulo operator (%) for index calculation. When we increment the `front` pointer or add new elements (incrementing the size), we are always wrapping around using modulo `capacity`.

For instance, when enqueueing, to find the index position to insert the new element, we compute `(front + size) % capacity`. When dequeueing, to move the `front` pointer, we simply do `(front + 1) % capacity`. This allows us to reuse the free space resulting from removing elements at the front.

The `enQueue` operation should insert a value at the rear of the queue. We first check if the queue is full by comparing `size` with `capacity`. If not full, we calculate the position to insert the new element, store the value, and increment `size`.

The `deQueue` operation should remove the value at the front of the queue. We check if the queue is empty if not, we move the `front` pointer forward and decrement `size`.

The `Front` operation returns the element at the `front` pointer if the queue isn't empty; otherwise, it returns `-1`.

The `Rear` operation calculates the position of the last element using `(front + size - 1) % capacity` and returns its value if the queue isn't empty; otherwise, it returns `-1`.

The `isEmpty` and `isFull` operations return the result of comparing the `size` to 0 and `capacity`, respectively.

This approach ensures all operations are completed in O(1) time complexity, which is optimal.

## Solution Approach

The solution utilizes an array, two integer variables (`front` and `size`), and modulo arithmetic to simulate a circular queue. The decision to use an array is based on the need for constant-time access to elements at both the front and rear ends of the queue.

Here is the detailed breakdown of the implementation:

### Initializing the Queue

• We define a constructor `__init__(self, k: int)` which accepts the capacity `k` of the queue.
• An array `self.q` with size `k` is initialized to store the elements. All values are initially set to 0, which acts as our storage space.
• The `self.front` variable is set to 0, pointing at the position that the first element of the queue should occupy initially.
• `self.size` represents the current number of elements and is initialized to 0.
• `self.capacity` is set to `k`, the maximum size of the queue.

### Enqueue Operation

• `enQueue(self, value: int)` is used to insert a new element at the rear of the queue.
• First, we check whether the queue is full by comparing `self.size` and `self.capacity`. If it is full, we return `False` as we cannot add more elements.
• If not full, we calculate the next rear position using `(self.front + self.size) % self.capacity` to get the appropriate index within our array bounds.
• Then we insert the new `value` into the calculated index and increase `self.size` by 1.
• We return `True` to indicate the operation succeeded.

### Dequeue Operation

• `deQueue(self)` is used to remove an element from the front of the queue.
• We check if the queue is empty by checking if `self.size` is 0. If it is, we return `False` as there are no elements to remove.
• If not empty, we update the `self.front` index to the next position using `(self.front + 1) % self.capacity`, effectively jumping over the front element, thus removing it from the queue.
• We decrement `self.size` by 1 and return `True` to indicate the operation succeeded.

### Front and Rear Operations

• `Front(self)` returns the value at the front of the queue without removing it. We calculate this simply by accessing `self.q[self.front]`, provided `self.size` is not 0; if it is, we return `-1`.
• `Rear(self)` returns the last element of the queue. We calculate the index of the last element using `(self.front + self.size - 1) % self.capacity` and return its value if the queue is not empty, otherwise return `-1`.

### Check if Empty or Full

• `isEmpty(self)` returns `True` if the queue is empty, which is the case when `self.size` is 0.
• `isFull(self)` returns `True` if the queue is full, which happens when `self.size` is equal to `self.capacity`.

These methods implement the fundamental operations of a circular queue and meet the constraints of the problem, with all operations having a time complexity of `O(1)`.

The solution leverages simple data structures and arithmetic properties to fulfill the requirements of the circular queue effectively and efficiently, without the need for any additional data structures or overly complex logic.

๐ช
Level Up Your
Algo Skills

### Example Walkthrough

Let's use an example to illustrate how the solution approach works. We'll create a circular queue with a capacity of 3 and walk through a sequence of operations.

1. Initialize the circular queue with capacity `k = 3`.

``1cq = MyCircularQueue(3)``

Now, `front = 0`, `size = 0`, and `capacity = 3`.

2. Attempt to enQueue the value `1` into the queue:

``1cq.enQueue(1)``

Since the queue is not full (`size < capacity`), we insert `1` at `(front + size) % capacity = (0 + 0) % 3 = 0`, the `cq.q` becomes `[1, 0, 0]`, `size` becomes `1`.

3. Enqueue the value `2` into the queue:

``1cq.enQueue(2)``

The queue is still not full, we insert `2` at `(front + size) % capacity = (0 + 1) % 3 = 1`, now `cq.q` is `[1, 2, 0]`, `size` becomes `2`.

4. Enqueue the value `3` into the queue:

``1cq.enQueue(3)``

Again, the queue is not full, we insert `3` at `(front + size) % capacity = (0 + 2) % 3 = 2`, `cq.q` becomes `[1, 2, 3]`, `size` becomes `3`.

5. Try to enQueue the value `4` into the queue:

``1cq.enQueue(4)``

The queue is full (`size == capacity`), so this returns `False`, and the queue remains unchanged.

6. Get the front value of the queue:

``1cq.Front()``

Returns `1` because `front = 0` and `cq.q[front] = 1`.

7. DeQueue an element from the queue:

``1cq.deQueue()``

The queue is not empty (`size > 0`), so we remove the front element by updating `front = (front + 1) % capacity = (0 + 1) % 3 = 1`, and `size` becomes `2`. The queue now conceptually looks like `[2, 3]`.

8. Get the rear value of the queue:

``1cq.Rear()``

Returns `3` because the rear index is `(front + size - 1) % capacity = (1 + 2 - 1) % 3 = 2`, and `cq.q[2] = 3`.

9. Check if the queue is empty:

``1cq.isEmpty()``

Returns `False` because `size = 2`, which is not `0`.

10. Check if the queue is full:

``1cq.isFull()``

Returns `False` because `size = 2` is less than `capacity = 3`.

Through these operations, we can see how the circular queue correctly implements the demand for a fixed-size, efficient data structure allowing front-end removal and rear-end insertion.

## Python Solution

``````1class MyCircularQueue:
2    def __init__(self, k: int):
3        self.queue = [0] * k  # Initialize the circular queue with 'k' elements set to 0
4        self.head_index = 0  # The front pointer to the first element of the circular queue
5        self.count = 0  # Current number of elements in the circular queue
6        self.capacity = k  # Maximum capacity of the circular queue
7
8    def enQueue(self, value: int) -> bool:
9        """Inserts an element into the circular queue. Return true if the operation is successful."""
10        if self.isFull():
11            # Return False if the queue is full
12            return False
13        # Calculate the index at which to insert the new value
14        idx = (self.head_index + self.count) % self.capacity
15        self.queue[idx] = value  # Insert the value
16        self.count += 1  # Increment the size of the queue
17        return True  # Return True on successful insertion
18
19    def deQueue(self) -> bool:
20        """Deletes an element from the circular queue. Return true if the operation is successful."""
21        if self.isEmpty():
22            # Return False if the queue is empty
23            return False
24        # Increment the head_index as we remove the front element
26        self.count -= 1  # Decrement the size of the queue
27        return True  # Return True on successful deletion
28
29    def Front(self) -> int:
30        """Gets the front item from the queue. If the queue is empty, return -1."""
31        if self.isEmpty():
32            # If the queue is empty, return -1
33            return -1
34        # Otherwise, return the front element
36
37    def Rear(self) -> int:
38        """Gets the last item from the queue. If the queue is empty, return -1."""
39        if self.isEmpty():
40            # If the queue is empty, return -1
41            return -1
42        # Calculate the index of the rear element
43        idx = (self.head_index + self.count - 1) % self.capacity
44        # Return the rear element
45        return self.queue[idx]
46
47    def isEmpty(self) -> bool:
48        """Checks whether the circular queue is empty."""
49        # Return True if count equals 0, meaning the queue is empty
50        return self.count == 0
51
52    def isFull(self) -> bool:
53        """Checks whether the circular queue is full."""
54        # Return True if count equals the capacity, meaning the queue is full
55        return self.count == self.capacity
56
57
58# Example of how the class could be used:
59# obj = MyCircularQueue(k)
60# success = obj.enQueue(value)
61# success = obj.deQueue()
62# item = obj.Front()
63# item = obj.Rear()
64# is_empty = obj.isEmpty()
65# is_full = obj.isFull()
66``````

## Java Solution

``````1class MyCircularQueue {
2    private int[] queue;     // Array representation of the circular queue
3    private int front;       // Index of the front element
4    private int size;        // Current size of the queue
5    private int capacity;    // Maximum capacity of the queue
6
7    // Constructor to initialize the queue with a given capacity.
8    public MyCircularQueue(int k) {
9        queue = new int[k];
10        capacity = k;
11        front = 0;
12        size = 0;
13    }
14
15    // Inserts an element into the circular queue. Returns true if the operation is successful.
16    public boolean enQueue(int value) {
17        if (isFull()) { // Check if the queue is full
18            return false;
19        }
20        // Calculate the index where the value will be inserted
21        int index = (front + size) % capacity;
22        queue[index] = value;
23        size++; // Increment the size of the queue
24        return true;
25    }
26
27    // Deletes an element from the circular queue. Returns true if the operation is successful.
28    public boolean deQueue() {
29        if (isEmpty()) { // Check if the queue is empty
30            return false;
31        }
32        front = (front + 1) % capacity; // Move the front pointer to the next element
33        size--; // Decrement the size of the queue
34        return true;
35    }
36
37    // Gets the front item from the queue. If the queue is empty, return -1.
38    public int Front() {
39        if (isEmpty()) { // Check if the queue is empty
40            return -1;
41        }
42        return queue[front]; // Return the front element
43    }
44
45    // Gets the last item from the queue. If the queue is empty, return -1.
46    public int Rear() {
47        if (isEmpty()) { // Check if the queue is empty
48            return -1;
49        }
50        // Calculate the index of the rear element
51        int index = (front + size - 1) % capacity;
52        return queue[index]; // Return the rear element
53    }
54
55    // Checks if the circular queue is empty.
56    public boolean isEmpty() {
57        return size == 0; // Return true if the size is 0
58    }
59
60    // Checks if the circular queue is full.
61    public boolean isFull() {
62        return size == capacity; // Return true if the size is equal to the capacity
63    }
64}
65
66/**
67 * Example of usage:
68 * MyCircularQueue circularQueue = new MyCircularQueue(3); // set the size to be 3
69 * boolean enQueueResult = circularQueue.enQueue(1);  // return true
70 * boolean deQueueResult = circularQueue.deQueue();  // return true
71 * int front = circularQueue.Front();  // return 1, or -1 if the queue is empty
72 * int rear = circularQueue.Rear();  // return the last element, or -1 if the queue is empty
73 * boolean isEmpty = circularQueue.isEmpty();  // return false
74 * boolean isFull = circularQueue.isFull();  // return true if the queue is full
75 */
76``````

## C++ Solution

``````1class MyCircularQueue {
2private:
3    int frontIndex;     // index of the front element
4    int currentSize;    // current number of elements in the queue
5    int maxCapacity;    // maximum capacity of the queue
6    vector<int> queue;  // vector to store the elements of the queue
7
8public:
9    // Constructor to initialize the queue with a fixed size.
10    MyCircularQueue(int k) {
11        maxCapacity = k;
12        queue = vector<int>(k);
13        frontIndex = currentSize = 0;
14    }
15
16    // Enqueue an element into the circular queue.
17    // Returns true if the operation is successful.
18    bool enQueue(int value) {
19        if (isFull()) return false;
20        int idx = (frontIndex + currentSize) % maxCapacity; // calculate index using modulo operation
21        queue[idx] = value; // place value at the calculated index
22        ++currentSize; // increment currentSize since we added an element
23        return true;
24    }
25
26    // Dequeue an element from the circular queue.
27    // Returns true if the operation is successful.
28    bool deQueue() {
29        if (isEmpty()) return false;
30        frontIndex = (frontIndex + 1) % maxCapacity; // update front index to the next element
31        --currentSize; // decrement currentSize to reflect removal
32        return true;
33    }
34
35    // Get the front item from the queue.
36    // Returns -1 if the queue is empty.
37    int Front() {
38        if (isEmpty()) return -1;
39        return queue[frontIndex];
40    }
41
42    // Get the last item from the queue.
43    // Returns -1 if the queue is empty.
44    int Rear() {
45        if (isEmpty()) return -1;
46        int idx = (frontIndex + currentSize - 1) % maxCapacity; // calculate index of the rear item
47        return queue[idx];
48    }
49
50    // Check if the queue is empty.
51    // Returns true if the queue is empty.
52    bool isEmpty() {
53        return currentSize == 0;
54    }
55
56    // Check if the queue is full.
57    // Returns true if the queue is full.
58    bool isFull() {
59        return currentSize == maxCapacity;
60    }
61};
62
63/**
64 * Your MyCircularQueue object will be instantiated and called as such:
65 * MyCircularQueue* obj = new MyCircularQueue(k);
66 * bool param_1 = obj->enQueue(value);
67 * bool param_2 = obj->deQueue();
68 * int param_3 = obj->Front();
69 * int param_4 = obj->Rear();
70 * bool param_5 = obj->isEmpty();
71 * bool param_6 = obj->isFull();
72 */
73``````

## Typescript Solution

``````1let queue: number[];
2let left: number;
3let right: number;
4let capacity: number;
5
6function initializeQueue(k: number): void {
7    queue = new Array(k);
8    left = 0;
9    right = 0;
10    capacity = k;
11}
12
13function enqueue(value: number): boolean {
14    if (isFull()) {
15        return false;
16    }
17    queue[right % capacity] = value;
18    right++;
19    return true;
20}
21
22function dequeue(): boolean {
23    if (isEmpty()) {
24        return false;
25    }
26    left++;
27    return true;
28}
29
30function front(): number {
31    if (isEmpty()) {
32        return -1;
33    }
34    return queue[left % capacity];
35}
36
37function rear(): number {
38    if (isEmpty()) {
39        return -1;
40    }
41    return queue[(right - 1) % capacity];
42}
43
44function isEmpty(): boolean {
45    return right - left === 0;
46}
47
48function isFull(): boolean {
49    return right - left === capacity;
50}
51
52// Usage example:
53// initializeQueue(3);
54// let successEnqueue = enqueue(10);
55// let successDequeue = dequeue();
56// let frontValue = front();
57// let rearValue = rear();
58// let isEmptyQueue = isEmpty();
59// let isFullQueue = isFull();
60``````

## Time and Space Complexity

### Time Complexity

• `__init__(self, k: int)`: The constructor has a time complexity of `O(k)` because it initializes an array of size `k`.

• `enQueue(self, value: int) -> bool`: Enqueue operation has a time complexity of `O(1)` because it involves arithmetic calculations for determining the index at which to insert the new element and inserting it into the queue, which are constant-time operations.

• `deQueue(self) -> bool`: Dequeue operation has a time complexity of `O(1)` since it only updates the `front` index and decreases the size, both of which are constant-time operations.

• `Front(self) -> int`: Retrieving the front item has a time complexity of `O(1)` because it directly accesses the element at the `front` index of the array.

• `Rear(self) -> int`: Retrieving the rear item has a time complexity of `O(1)` as it involves calculating the index of the rear and accessing it which are constant-time operations.

• `isEmpty(self) -> bool`: Checking if the queue is empty has a time complexity of `O(1)` as it's a simple comparison operation.

• `isFull(self) -> bool`: Checking if the queue is full has a time complexity of `O(1)` since it is also a comparison operation.

### Space Complexity

• The space complexity for the entire `MyCircularQueue` class is `O(k)` where `k` is the capacity of the queue. This is due to the array of size `k` used for storing the queue elements.
๐
Become an
Algo Monster

Got a question?ย Ask the Teaching Assistantย anything you don't understand.

Still not clear? Ask in the Forum, ย Discordย orย Submitย the part you don't understand to our editors.

โ
โTA ๐จโ๐ซ