Implement a class MyCircularQueue with capacity k. The queue must support enQueue, deQueue, Front, Rear, isEmpty, and isFull. The challenge is distinguishing full from empty while reusing array slots after deletions.
β’ 1 β€ k β€ 1000β’ 0 β€ value β€ 1000β’ At most 3000 calls are made to enQueue, deQueue, Front, Rear, isEmpty, and isFull
02
Section Two Β· Brute Force
Shifting Array Queue β O(n) deQueue
Store elements from index 0 to size - 1. enQueue writes at the end, but deQueue shifts every remaining element one step left. This satisfies the interface, but it throws away the entire point of a circular queue.
Why it works
Because the logical front is always kept at index 0, Front() and Rear() are trivial. Capacity checks also become easy: full means size == k, empty means size == 0.
Why we can do better
Problem: Shifting makes every deletion O(n). The whole design problem exists to avoid moving data. A ring buffer keeps elements in place and moves only the metadata: head and size.
Java β shifting array
classMyCircularQueue {
private finalint[] data;
privateint size = 0;
publicMyCircularQueue(int k) {
data = newint[k];
}
public booleanenQueue(int value) {
if (isFull()) return false;
data[size++] = value;
return true;
}
public booleandeQueue() {
if (isEmpty()) return false;
for (int i = 1; i < size; i++)
data[i - 1] = data[i];
size--;
return true;
}
publicintFront() { returnisEmpty() ? -1 : data[0]; }
publicintRear() { returnisEmpty() ? -1 : data[size - 1]; }
public booleanisEmpty() { return size == 0; }
public booleanisFull() { return size == data.length; }
}
Keep the array fixed. Instead of moving elements, track where the logical front starts and how many items are currently stored. The next enqueue position is (head + size) % capacity. The rear element lives at (head + size - 1 + capacity) % capacity.
π‘ Mental model: Picture numbered seats around a merry-go-round. Riders do not physically slide when the front rider gets off. You simply move the βfrontβ sticker to the next seat. When a new rider boards, they take the seat immediately after the current rear, wrapping around to seat 0 when needed.
Algorithm β constant-time wraparound
State: store data[], head, size, and capacity.
enQueue(value): if full, return false; otherwise write to (head + size) % capacity and increment size.
deQueue(): if empty, return false; otherwise move head = (head + 1) % capacity and decrement size.
Front(): return data[head] when not empty.
Rear(): compute the logical last index with modulo normalization.
π― When to recognize this pattern:
Fixed capacity plus repeated front deletions is the giveaway.
If an interface says βbounded queue,β βwrap around,β or βreuse freed slots,β you want a ring buffer instead of shifting elements or allocating linked nodes.
Why keep size?:
Without extra state, head == tail is ambiguous β it can mean either empty or full.
Tracking size removes that ambiguity cleanly and keeps every method O(1).
04
Section Four Β· Walkthrough
Visual Walkthrough
Circular Queue β head stays logical, writes wrap physically
05
Section Five Β· Code
Code β Java & Python
Java β ring buffer
classMyCircularQueue {
private finalint[] data;
private finalint capacity;
privateint head = 0;
privateint size = 0;
publicMyCircularQueue(int k) {
data = newint[k];
capacity = k;
}
public booleanenQueue(int value) {
if (isFull()) return false;
int tail = (head + size) % capacity;
data[tail] = value;
size++;
return true;
}
public booleandeQueue() {
if (isEmpty()) return false;
head = (head + 1) % capacity;
size--;
return true;
}
publicintFront() {
returnisEmpty() ? -1 : data[head];
}
publicintRear() {
if (isEmpty()) return -1;
int tail = (head + size - 1 + capacity) % capacity;
return data[tail];
}
public booleanisEmpty() {
return size == 0;
}
public booleanisFull() {
return size == capacity;
}
}
Easy state management, but every front removal moves data.
Linked list + capacity
O(1)
O(k)
Also works, but allocates one node per element and misses the ring-buffer lesson.
Ring buffer β optimal
O(1) all methods
O(k)
No shifts, no extra nodes, and wraparound is handled with modulo arithmetic.
Why not keep a moving tail pointer only?:
You can, but then you still need either a size field or one permanently unused slot to separate full from empty.
The head + size formulation is compact and less bug-prone in interviews.
07
Section Seven Β· Edge Cases
Edge Cases & Pitfalls
Case
Expected behavior
Why it matters
k = 1
The only slot alternates between full and empty
Small capacities expose off-by-one mistakes quickly.
Queue is empty
deQueue() returns false, Front()/Rear() return -1
The interface uses sentinel values instead of exceptions.
Queue is full
enQueue() returns false
You must reject writes instead of overwriting existing data.
Wraparound after deletions
Freed slots at the front are reused
This is the entire advantage over a naive array queue.
Repeated cycles of fill β empty β fill
All operations stay O(1)
Persistent correctness matters more than one successful example.
β Common Mistake: Using head == tail to mean βemptyβ without any extra state breaks as soon as the queue becomes full, because a full ring can also make the indices equal. Track size explicitly, or intentionally waste one slot and code the full condition differently.