Design your implementation of the circular queue. The circular queue is a linear data structure in which the operations are performed based on FIFO (First In First Out) principle and the last position is connected back to the first position to make a circle. It is also called “Ring Buffer”.
One of the benefits of the circular queue is that we can make use of the spaces in front of the queue. In a normal queue, once the queue becomes full, we cannot insert the next element even if there is a space in front of the queue. But using the circular queue, we can use the space to store new values.
Your implementation should support following operations:
MyCircularQueue(k): Constructor, set the size of the queue to be k.
Front: Get the front item from the queue. If the queue is empty, return -1.
Rear: Get the last item from the queue. If the queue is empty, return -1.
enQueue(value): Insert an element into the circular queue. Return true if the operation is successful.
deQueue(): Delete an element from the circular queue. Return true if the operation is successful.
isEmpty(): Checks whether the circular queue is empty or not.
isFull(): Checks whether the circular queue is full or not.
Time Complexity: O(N) for deQueue, O(1) for the rest
Space Complexity: O(N)
The easiest and most straightforward solution for the circular queue is to use an array. Your mileage may vary depending on which programming language you’re using, but in python, [] also acts similar like a queue due to public methods, such as pop, and push.
To implement the circular queue, we just need a capacity to hold k, which is the maximum size of the circular queue. For enQueue and deQueue, we can leverage the builtin pop to remove an element and push to add an element.
Complexity Explanation
It should be clear why the time complexity of all of the methods are O(1), except for deQueue, which is O(N). deQueue uses pop(0), which removes the first element from the array, forcing rest of the elements to be shifted.
The space complexity is O(N) or O(k) because our circular queue can hold k elements,
The previous solution is kind of cheating because we’re using builtin methods for []. To implement a circular queue without using those builtin methods, we can use two pointers to keep track of the beginning and the end of the array. This solution can be implemented in almost any programming language.
To implement this, we need:
A queue array initialized with the size of k.
A size to count the current number of elements in the circular queue.
A capacity to hold k.
Two pointers, head and tail to keep track of the front and end of the circular queue.
Everything is similar to the previous solution, except for enQueue and deQueue.
enQueue will increment the tail and save the value. It is important that we also mod/% the tail with the capacity to handle the case when tail > capacity. Because of this, we initialize tail to -1, since initially, there’s nothing inside the array and the first enQueue will place value at tail when tail = 0. We also need to increment the size.
Similarly, deQueue will do the same thing as enQueue except with the head. Although, there are two key differences.
head is initialized to 0 because in the case when we call Front, we need the first element, which is in the first index.
We need to decrement the size instead of incrementing.
Complexity Explanation
Unlike the previous solution, all the methods in this solution are O(1), especially deQueue because we replaced pop(0) with an indexing solution, which is constant.
The space complexity is still O(N) or O(k) with a little bit more constant overhead from the introduction of additional variables.