Stacks & Queues · 25 of 38
Implement a Circular queue
Make a waiting line in a fixed row of seats that bends around like a merry-go-round, so seats freed at the front get used again at the back.
- Simple way
- time O(n) per dequeue
room O(k) - Clever way
- time O(1) per operation
room O(k)
The problem, simply
Build a queue that holds at most k values, using one array of size k. You need:
enQueue(x): add at the back. Returnfalseif full.deQueue(): remove from the front. Returnfalseif empty.Front()andRear(): the values at each end, or-1if empty.isEmpty()andisFull().
Every action must be O(1), and a box freed at the front must be usable again.
A story to remember it
Picture it
Capacity 4. The line is 30, 40, 50, 60. front is at box 2, and rear has wrapped around to box 1. Reading from front and going around gives the order.
- looking now
Examples
Fill, overflow, reuse
- In
k = 3: enQueue 1, 2, 3, 4; Rear(); isFull(); deQueue(); enQueue(4); Rear(); Front()- Out
true, true, true, false; 3; true; true; true; 4; 2
The fourth enQueue fails because the line is full. After one deQueue, the freed box is used for 4.
Wrapping around
- In
k = 4: enQueue 10, 20, 30, 40; deQueue twice; enQueue 50, 60; Front(); Rear()- Out
Front = 30, Rear = 60
50 and 60 land in boxes 0 and 1, which 10 and 20 just left.
Empty queue
- In
k = 2: deQueue(); Front(); Rear()- Out
false, -1, -1
Nothing to remove or read.
Capacity one
- In
k = 1: enQueue(5); enQueue(6); deQueue(); enQueue(6); Front()- Out
true, false, true, true, 6
front and rear are always box 0. The count alone tells full from empty.
Way 1: the simple way
Keep the line packed at the start of the array. The front is always box 0. When the front leaves, shift everyone one box left.
enQueue: write atarr[size]and add one tosize.deQueue: movearr[1..size-1]one step left, take one fromsize.Frontisarr[0],Rearisarr[size - 1].
After deQueue in Way 1, 20, 30 and 40 must all slide left. The line stays packed, but every removal costs a slide of the whole line.
- checking
class ShiftingQueue {
private final int[] arr;
private int size = 0;
ShiftingQueue(int k) { arr = new int[k]; }
public boolean enQueue(int x) {
if (isFull()) return false;
arr[size++] = x;
return true;
}
public boolean deQueue() {
if (isEmpty()) return false;
for (int i = 1; i < size; i++) arr[i - 1] = arr[i]; // everyone steps left
size--;
return true;
}
public int Front() { return isEmpty() ? -1 : arr[0]; }
public int Rear() { return isEmpty() ? -1 : arr[size - 1]; }
public boolean isEmpty() { return size == 0; }
public boolean isFull() { return size == arr.length; }
}How long it takes: deQueue shifts up to k - 1 values, O(k). The rest is O(1). Extra room: the array, O(k).
Way 2: the clever way
Do not move the values. Move two pointers and let them wrap around with % k.
- Keep
front(the oldest value),rear(the newest value) andcount. Start withfront = 0,rear = -1,count = 0. enQueue(x): ifcount == k, returnfalse. Elserear = (rear + 1) % k, writearr[rear] = x, add one tocount.deQueue(): ifcount == 0, returnfalse. Elsefront = (front + 1) % k, take one fromcount.Front()isarr[front],Rear()isarr[rear]. Empty iscount == 0, full iscount == k.
The % k is what bends the row into a circle: after box k - 1 comes box 0.
A line of capacity 4 going around the circle
01 / 11
Start. front is box 0, rear is just before box 0, and count is 0.
- count
- 0
- looking now
- done
- just added
- ignored
class MyCircularQueue {
private final int[] arr;
private int front = 0, rear = -1, count = 0;
public MyCircularQueue(int k) { arr = new int[k]; }
public boolean enQueue(int x) {
if (isFull()) return false;
rear = (rear + 1) % arr.length; // step forward, wrap at the end
arr[rear] = x;
count++;
return true;
}
public boolean deQueue() {
if (isEmpty()) return false;
front = (front + 1) % arr.length; // the oldest leaves
count--;
return true;
}
public int Front() { return isEmpty() ? -1 : arr[front]; }
public int Rear() { return isEmpty() ? -1 : arr[rear]; }
public boolean isEmpty() { return count == 0; }
public boolean isFull() { return count == arr.length; }
}How long it takes: every action moves one pointer and changes the count, O(1). Extra room: the array of k boxes, O(k).
Watch out for
- Write
(rear + 1) % k, notrear + 1 % k. The%binds tighter than+. Rear()must readarr[rear], notarr[rear - 1]and notarr[(front + count) % k], which is the next free box.- If you compute rear from front, use
(front + count - 1) % k. Withfront = 0andcount = 0that is-1 % k, which is negative in Java, so guard the empty case. - The old value stays in the array after
deQueue. That is fine, it will be written over.
What interviewers ask next
- Can you drop the
count? Yes. Either keep one box empty, or keepfrontandsizeonly and computerear = (front + size - 1) % k. - Make it grow. When full, make an array twice as big and copy the values in queue order starting at index 0. That is how
ArrayDequeworks. - A circular deque, with adding and removing at both ends. Moving
frontbackward is(front - 1 + k) % k; addkbefore%to avoid negatives. - Make it safe for two threads, one adding and one removing. A ring buffer with one writer and one reader can work without locks, using only the two indices. That is common in audio, logging and network drivers.
- Why is it called a ring buffer? The array is used as if its two ends were joined into a ring.
Remember this
Grown-up words: the merry-go-round is a circular queue or ring buffer, the two fingers are the front and rear indices, wrapping around is modulo arithmetic, and sliding everyone forward in Way 1 is an O(n) shift.