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.
Implement the MyCircularQueue
class:
MyCircularQueue(k)
Initializes the object with the size of the queue to be k
.int Front()
Gets the front item from the queue. If the queue is empty, return -1
.int Rear()
Gets the last item from the queue. If the queue is empty, return -1
.boolean enQueue(int value)
Inserts an element into the circular queue. Return true
if the operation is successful.boolean deQueue()
Deletes an element from the circular queue. Return true
if the operation is successful.boolean isEmpty()
Checks whether the circular queue is empty or not.boolean isFull()
Checks whether the circular queue is full or not.Example 1:
Input
["MyCircularQueue", "enQueue", "enQueue", "enQueue", "enQueue", "Rear", "isFull", "deQueue", "enQueue", "Rear"]
[[3], [1], [2], [3], [4], [], [], [], [4], []]
Output
[null, true, true, true, false, 3, true, true, true, 4]
Explanation
MyCircularQueue myCircularQueue = new MyCircularQueue(3);
myCircularQueue.enQueue(1); // return True
myCircularQueue.enQueue(2); // return True
myCircularQueue.enQueue(3); // return True
myCircularQueue.enQueue(4); // return False
myCircularQueue.Rear(); // return 3
myCircularQueue.isFull(); // return True
myCircularQueue.deQueue(); // return True
myCircularQueue.enQueue(4); // return True
myCircularQueue.Rear(); // return 4
Constraints:
1 <= k <= 1000
0 <= value <= 1000
3000
calls will be made to enQueue
, deQueue
, Front
, Rear
, isEmpty
, and isFull
.When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:
Think of a circular queue like a waiting line that loops around. A brute force method for managing this line involves manually checking every possible position for new people and removals, without trying to be clever or efficient.
Here's how the algorithm would work step-by-step:
class MyCircularQueue:
def __init__(self, queue_capacity: int):
self.queue_capacity = queue_capacity
self.queue = [None] * queue_capacity
self.queue_size = 0
def enQueue(self, value: int) -> bool:
if self.isFull():
return False
# Find the next available spot
for index in range(self.queue_capacity):
potential_spot = index % self.queue_capacity
if self.queue[potential_spot] is None:
self.queue[potential_spot] = value
self.queue_size += 1
return True
return False
def deQueue(self) -> bool:
if self.isEmpty():
return False
# Check every element to remove the first
for index in range(self.queue_capacity):
potential_spot = index % self.queue_capacity
if self.queue[potential_spot] is not None:
self.queue[potential_spot] = None
self.queue_size -= 1
return True
return False
def Front(self) -> int:
if self.isEmpty():
return -1
# Find the first element
for index in range(self.queue_capacity):
potential_spot = index % self.queue_capacity
if self.queue[potential_spot] is not None:
return self.queue[potential_spot]
return -1
def Rear(self) -> int:
if self.isEmpty():
return -1
# Find the last element by reverse iteration
for index in range(self.queue_capacity - 1, -1, -1):
potential_spot = index % self.queue_capacity
if self.queue[potential_spot] is not None:
return self.queue[potential_spot]
return -1
def isEmpty(self) -> bool:
return self.queue_size == 0
def isFull(self) -> bool:
return self.queue_size == self.queue_capacity
# Your MyCircularQueue object will be instantiated and called as such:
# obj = MyCircularQueue(k)
# param_1 = obj.enQueue(value)
# param_2 = obj.deQueue()
# param_3 = obj.Front()
# param_4 = obj.Rear()
# param_5 = obj.isEmpty()
# param_6 = obj.isFull()
The idea is to simulate a queue within a fixed-size container, but with a clever twist: once you reach the end, you wrap back to the beginning. This saves space by reusing emptied spots, and allows us to avoid constantly shifting data.
Here's how the algorithm would work step-by-step:
class CircularQueue:
def __init__(self, queue_capacity):
self.queue_capacity = queue_capacity
self.queue = [None] * queue_capacity
self.head_index = 0
self.tail_index = 0
self.queue_size = 0
def enqueue(self, value):
if self.isFull():
return False
self.queue[self.tail_index] = value
self.tail_index = (self.tail_index + 1) % self.queue_capacity
self.queue_size += 1
return True
def dequeue(self):
if self.isEmpty():
return False
self.head_index = (self.head_index + 1) % self.queue_capacity
self.queue_size -= 1
return True
def Front(self):
if self.isEmpty():
return -1
return self.queue[self.head_index]
def Rear(self):
if self.isEmpty():
return -1
rear_index = (self.tail_index - 1) % self.queue_capacity
return self.queue[rear_index]
def isEmpty(self):
return self.queue_size == 0
def isFull(self):
return self.queue_size == self.queue_capacity
Case | How to Handle |
---|---|
Queue is full when enqueueing | Return false to indicate enqueue failure without modifying the queue state. |
Queue is empty when dequeueing or peeking | Return -1 (or throw an exception in some languages) to indicate an empty queue operation. |
Queue is initialized with a zero or negative capacity | Throw an IllegalArgumentException or return an error code to indicate invalid input. |
Enqueueing and dequeueing repeatedly, nearing max integer value for head/tail | The modulo operation ensures head and tail wrap around correctly, preventing integer overflow issues during indexing. |
Capacity is 1 (minimum size) | Handle full and empty queue states carefully, paying attention to head == tail condition. |
Many enqueue and dequeue operations such that tail 'catches up' to head | The isFull() and isEmpty() methods must correctly account for the modulo wrap-around. |
Integer overflow when calculating next position for head or tail | The modulo operator (%) prevents overflow by wrapping indices within the queue's capacity. |
Calling peek when the queue is logically full (head == tail, but size is supposed to be 0) | The isEmpty() check must preempt peek() operations when head and tail are equal and the queue's size is 0. |