Design and implement 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 <= 10003000 calls will be made to enQueue, deQueue, Front, Rear, isEmpty, and isFull.Before attempting this problem, you should be comfortable with:
The simplest approach is to use a dynamic array and treat it like a regular queue. We add elements to the back and remove from the front. While this works, removing from the front requires shifting all remaining elements, making it inefficient. We check the array size against the capacity to determine if the queue is full.
q and store the capacity k.enQueue(value): If the array size equals k, return false. Otherwise, append the value and return true.deQueue(): If the array is empty, return false. Otherwise, remove the first element (index 0) and return true.Front(): Return the first element if the array is non-empty, otherwise return -1.Rear(): Return the last element if the array is non-empty, otherwise return -1.isEmpty(): Return true if the array size is 0.isFull(): Return true if the array size equals k.class MyCircularQueue:
def __init__(self, k: int):
self.q = []
self.k = k
def enQueue(self, value: int) -> bool:
if len(self.q) == self.k:
return False
self.q.append(value)
return True
def deQueue(self) -> bool:
if not self.q:
return False
self.q.pop(0)
return True
def Front(self) -> int:
if self.q:
return self.q[0]
return -1
def Rear(self) -> int:
if self.q:
return self.q[-1]
return -1
def isEmpty(self) -> bool:
return len(self.q) == 0
def isFull(self) -> bool:
return len(self.q) == self.kWhere is the size of the queue.
To achieve O(1) operations, we use a fixed-size array with two pointers: front pointing to the first element and rear pointing to the last. The "circular" aspect comes from using modulo arithmetic so that when we reach the end of the array, we wrap around to the beginning. We track the current size separately to distinguish between empty and full states.
k, set front = 0, rear = -1, and size = 0.enQueue(value): If full, return false. Otherwise, compute rear = (rear + 1) % k, store the value at that index, increment size, and return true.deQueue(): If empty, return false. Otherwise, compute front = (front + 1) % k, decrement size, and return true.Front(): If empty, return -1. Otherwise, return q[front].Rear(): If empty, return -1. Otherwise, return q[rear].isEmpty(): Return true if size equals 0.isFull(): Return true if size equals k.class MyCircularQueue:
def __init__(self, k: int):
self.q = [0] * k
self.k = k
self.front = 0
self.rear = -1
self.size = 0
def enQueue(self, value: int) -> bool:
if self.isFull():
return False
self.rear = (self.rear + 1) % self.k
self.q[self.rear] = value
self.size += 1
return True
def deQueue(self) -> bool:
if self.isEmpty():
return False
self.front = (self.front + 1) % self.k
self.size -= 1
return True
def Front(self) -> int:
if self.isEmpty():
return -1
return self.q[self.front]
def Rear(self) -> int:
if self.isEmpty():
return -1
return self.q[self.rear]
def isEmpty(self) -> bool:
return self.size == 0
def isFull(self) -> bool:
return self.size == self.kWhere is the size of the queue.
A doubly linked list allows O(1) insertions and deletions at both ends. We use dummy head and tail nodes to simplify edge cases. New elements are inserted before the tail (at the rear), and elements are removed after the head (from the front). We track remaining space to know when the queue is full.
left and right connected to each other, and set space = k.enQueue(value): If full, return false. Create a new node, insert it between right.prev and right, decrement space, and return true.deQueue(): If empty, return false. Remove the node after left by updating pointers, increment space, and return true.Front(): If empty, return -1. Otherwise, return left.next.val.Rear(): If empty, return -1. Otherwise, return right.prev.val.isEmpty(): Return true if left.next equals right.isFull(): Return true if space equals 0.class ListNode:
def __init__(self, val, nxt, prev):
self.val, self.next, self.prev = val, nxt, prev
class MyCircularQueue:
def __init__(self, k: int):
self.space = k
self.left = ListNode(0, None, None)
self.right = ListNode(0, None, self.left)
self.left.next = self.right
def enQueue(self, value: int) -> bool:
if self.isFull(): return False
cur = ListNode(value, self.right, self.right.prev)
self.right.prev.next = cur
self.right.prev = cur
self.space -= 1
return True
def deQueue(self) -> bool:
if self.isEmpty(): return False
self.left.next = self.left.next.next
self.left.next.prev = self.left
self.space += 1
return True
def Front(self) -> int:
if self.isEmpty(): return -1
return self.left.next.val
def Rear(self) -> int:
if self.isEmpty(): return -1
return self.right.prev.val
def isEmpty(self) -> bool:
return self.left.next == self.right
def isFull(self) -> bool:
return self.space == 0Where is the size of the queue.
A singly linked list can also work, using less memory per node than a doubly linked list. We maintain a dummy head node and a pointer to the actual tail. New elements are added at the tail, and elements are removed from the front (after the dummy head). The only complication is updating the tail pointer when the queue becomes empty.
left, set right = left, and set space = k.enQueue(value): If full, return false. Create a new node. If empty, set it as left.next and right. Otherwise, link it after right and update right. Decrement space and return true.deQueue(): If empty, return false. Remove left.next by updating left.next to left.next.next. If left.next becomes null, reset right = left. Increment space and return true.Front(): If empty, return -1. Otherwise, return left.next.val.Rear(): If empty, return -1. Otherwise, return right.val.isEmpty(): Return true if left.next is null.isFull(): Return true if space equals 0.class ListNode:
def __init__(self, val, nxt=None):
self.val = val
self.next = nxt
class MyCircularQueue:
def __init__(self, k: int):
self.space = k
self.left = ListNode(0)
self.right = self.left
def enQueue(self, value: int) -> bool:
if self.isFull(): return False
cur = ListNode(value)
if self.isEmpty():
self.left.next = cur
self.right = cur
else:
self.right.next = cur
self.right = cur
self.space -= 1
return True
def deQueue(self) -> bool:
if self.isEmpty(): return False
self.left.next = self.left.next.next
if self.left.next is None:
self.right = self.left
self.space += 1
return True
def Front(self) -> int:
if self.isEmpty(): return -1
return self.left.next.val
def Rear(self) -> int:
if self.isEmpty(): return -1
return self.right.val
def isEmpty(self) -> bool:
return self.left.next is None
def isFull(self) -> bool:
return self.space == 0Where is the size of the queue.
The circular nature requires careful modulo operations. A common mistake is forgetting to apply modulo when updating pointers, causing index out of bounds errors.
# Wrong - no wrap-around
def enQueue(self, value: int) -> bool:
if self.isFull():
return False
self.rear += 1 # Will exceed array bounds!
self.q[self.rear] = value
self.size += 1
return True
# Correct - use modulo for circular behavior
def enQueue(self, value: int) -> bool:
if self.isFull():
return False
self.rear = (self.rear + 1) % self.k
self.q[self.rear] = value
self.size += 1
return TrueWhen using only front and rear pointers without a separate size variable, distinguishing between empty and full queue becomes ambiguous since both states can have front == rear.
# Problematic - can't distinguish empty from full
def isEmpty(self) -> bool:
return self.front == self.rear # Also true when full!
# Solution: Track size separately
def isEmpty(self) -> bool:
return self.size == 0
def isFull(self) -> bool:
return self.size == self.kInitializing rear to 0 instead of -1 causes the first element to be placed at index 1, wasting space and causing incorrect behavior.
# Wrong initialization
def __init__(self, k: int):
self.q = [0] * k
self.front = 0
self.rear = 0 # First enQueue goes to index 1!
self.size = 0
# Correct initialization
def __init__(self, k: int):
self.q = [0] * k
self.front = 0
self.rear = -1 # First enQueue goes to index 0
self.size = 0
Sign in to join the discussion