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 <= 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:
Imagine having a container with a fixed number of slots, and we want to add or remove items. The brute force way involves meticulously tracking each item and checking if adding or removing goes beyond the container's capacity. This is done without clever shortcuts, just step-by-step management.
Here's how the algorithm would work step-by-step:
class MyCircularQueue:
def __init__(self, capacity_value: int):
self.queue_container = [None] * capacity_value
self.queue_capacity = capacity_value
self.queue_head = -1
self.queue_tail = -1
def enqueue(self, value: int) -> bool:
# Check if the queue is full before enqueuing
if self.is_full():
return False
if self.is_empty():
self.queue_head = 0
self.queue_tail = (self.queue_tail + 1) % self.queue_capacity
self.queue_container[self.queue_tail] = value
return True
def dequeue(self) -> bool:
# Check if the queue is empty before dequeuing
if self.is_empty():
return False
if self.queue_head == self.queue_tail:
self.queue_head = -1
self.queue_tail = -1
else:
# Move the head to the next element
self.queue_head = (self.queue_head + 1) % self.queue_capacity
return True
def front(self) -> int:
if self.is_empty():
return -1
return self.queue_container[self.queue_head]
def rear(self) -> int:
if self.is_empty():
return -1
return self.queue_container[self.queue_tail]
def is_empty(self) -> bool:
return self.queue_head == -1
def is_full(self) -> bool:
# Need to check full condition before enqueuing
return ((self.queue_tail + 1) % self.queue_capacity) == self.queue_head
Imagine the queue as a circle instead of a straight line. To efficiently manage adding and removing items, we'll use special pointers to keep track of the beginning and end of the stored data in the circle.
Here's how the algorithm would work step-by-step:
class MyCircularQueue:
def __init__(self, queue_size: int):
self.queue_array = [None] * queue_size
self.head_index = 0
self.tail_index = 0
self.queue_size = queue_size
self.current_size = 0
def enQueue(self, value: int) -> bool:
if self.isFull():
return False
self.queue_array[self.tail_index] = value
self.tail_index = (self.tail_index + 1) % self.queue_size
self.current_size += 1
return True
def deQueue(self) -> bool:
if self.isEmpty():
return False
self.head_index = (self.head_index + 1) % self.queue_size
self.current_size -= 1
return True
def Front(self) -> int:
if self.isEmpty():
return -1
return self.queue_array[self.head_index]
def Rear(self) -> int:
if self.isEmpty():
return -1
# Calculate the index of the rear element
rear_index = (self.tail_index - 1 + self.queue_size) % self.queue_size
return self.queue_array[rear_index]
def isEmpty(self) -> bool:
# Check if the queue is empty
return self.current_size == 0
def isFull(self) -> bool:
# Check if the queue is full
return self.current_size == self.queue_size
Case | How to Handle |
---|---|
k is zero or negative | Throw an IllegalArgumentException, as a circular queue cannot have a non-positive size. |
Multiple enqueue operations filling the queue to capacity, then multiple dequeue operations emptying the queue. | Verify that head and tail pointers are correctly updated to wrap around and elements are properly enqueued and dequeued without data loss or incorrect pointers. |
Enqueue operation when the queue is already full. | Return false from the enqueue operation, indicating that the enqueue failed. |
Dequeue operation when the queue is empty. | Return false from the dequeue operation, indicating that the dequeue failed. |
Calling head() or tail() when the queue is empty. | Return -1 as specified in the prompt, indicating there is no element to return. |
Extreme number of enqueue and dequeue operations leading to head and tail wrapping around multiple times. | Ensure that the modulo operator (%) is used correctly when calculating new positions for head and tail to prevent out-of-bounds access and maintain circularity. |
Integer overflow for queue size k, especially during index calculations. | Use `int` for index calculations; ensure k itself doesn't exceed reasonable memory constraints before instantiating the array. |
Rapid sequence of enqueue and dequeue operations that continuously switch between empty and full states. | Test for race conditions (if multithreaded), make sure the `isEmpty` and `isFull` methods update correctly after each op to prevent inconsistencies. |