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.You must solve the problem without using the built-in queue data structure in your programming language.
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 <= 10000 <= value <= 10003000 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:
To design a circular queue, the brute force approach involves creating a fixed-size storage and carefully managing where items are added and removed to simulate a wrap-around behavior. This means tracking the front and back of the queue and handling cases where the queue fills up or becomes empty by cleverly reusing space.
Here's how the algorithm would work step-by-step:
class CircularQueue:
def __init__(self, capacity):
self.queue_storage = [None] * capacity
self.queue_capacity = capacity
self.front_index = 0
self.back_index = -1
self.current_size = 0
def enqueue(self, item):
if self.is_full():
return False
# Increment back_index and wrap around if necessary
self.back_index = (self.back_index + 1) % self.queue_capacity
self.queue_storage[self.back_index] = item
self.current_size += 1
return True
def dequeue(self):
if self.is_empty():
return False
# Remove item from the front and advance front_index, wrapping around
removed_item = self.queue_storage[self.front_index]
self.queue_storage[self.front_index] = None # Optional: clear removed item
self.front_index = (self.front_index + 1) % self.queue_capacity
self.current_size -= 1
return removed_item
def peek(self):
if self.is_empty():
return None
return self.queue_storage[self.front_index]
def is_empty(self):
return self.current_size == 0
def is_full(self):
return self.current_size == self.queue_capacityA circular queue behaves like a regular queue but wraps around. The clever trick is to use a fixed storage space and manage two pointers to keep track of the front and back of the queue, allowing it to reuse space efficiently.
Here's how the algorithm would work step-by-step:
class CircularQueue:
def __init__(self, queue_size):
self.queue_storage = [None] * queue_size
self.queue_capacity = queue_size
self.front_pointer = 0
self.rear_pointer = 0
self.current_item_count = 0
def enqueue_item(self, item_to_add):
if self.is_queue_full():
return False
self.queue_storage[self.rear_pointer] = item_to_add
self.rear_pointer = (self.rear_pointer + 1) % self.queue_capacity
self.current_item_count += 1
return True
def dequeue_item(self):
if self.is_queue_empty():
return False
# When dequeuing, we retrieve the item at the front pointer.
removed_item = self.queue_storage[self.front_pointer]
self.queue_storage[self.front_pointer] = None # Optional: Clear the dequeued slot
self.front_pointer = (self.front_pointer + 1) % self.queue_capacity
self.current_item_count -= 1
return removed_item
def peek_front_item(self):
if self.is_queue_empty():
return None
return self.queue_storage[self.front_pointer]
def peek_rear_item(self):
if self.is_queue_empty():
return None
# The rear pointer points to the next available slot, so we need to go back one.
return self.queue_storage[(self.rear_pointer - 1 + self.queue_capacity) % self.queue_capacity]
def is_queue_empty(self):
# The queue is empty if no items have been added or all have been removed.
return self.current_item_count == 0
def is_queue_full(self):
# The queue is full when the number of items equals its capacity.
return self.current_item_count == self.queue_capacity
def get_queue_size(self):
return self.current_item_count
| Case | How to Handle |
|---|---|
| Queue size k is 0 or negative | The constructor should validate k and throw an error or handle it gracefully, as a queue of non-positive size is invalid. |
| Enqueueing into a full queue | The enqueue operation should return false and not modify the queue state when isFull returns true. |
| Dequeueing from an empty queue | The dequeue operation should return false and not modify the queue state when isEmpty returns true. |
| Front or Rear operation on an empty queue | These operations must return -1 as specified when the queue is empty. |
| Circular wrapping of front and rear pointers | Modulo arithmetic (e.g., (index + 1) % k) must be correctly applied to ensure pointers wrap around the array. |
| Queue transitions from empty to full, and full to empty | The logic for isEmpty and isFull needs to accurately reflect the queue's state after enqueues and dequeues, especially when only one element is present. |
| Maximum k value leading to potential integer overflow if not handled | While k is an integer, the indices and count within the queue implementation should use appropriate data types to avoid overflow if k is very large. |
| Enqueuing and immediately dequeueing the same element | The implementation must correctly handle the scenario where the front and rear pointers might become the same after these operations, ensuring isEmpty and isFull are accurate. |