#641 Design Circular Deque
frequently reported atAmazon
the problem
In plain words
Implement a fixed-capacity double-ended queue MyCircularDeque(k): insertFront, insertLast, deleteFront, deleteLast (return true/false), getFront, getRear (−1 if empty), isEmpty, isFull.
in k = 3; insertLast(1), insertLast(2), insertFront(3), insertFront(4), getRear(), isFull(), deleteLast(), insertFront(4), getFront()
out true, true, true, false, 2, true, true, true, 4
- 1 ≤ k ≤ 1000
- 0 ≤ value ≤ 1000
- At most 2000 calls
your turn
Try it before you peek
Read the examples, say the brute force out loud, then write code: here or on LeetCode ↗. Stuck for 20 minutes? Open one hint at a time.
Solve it in the browser
Design: implement every operation. Compiled by the real javac (Java 17) and judged right here — nothing is sent anywhere.
Solutions are hidden so you can try first. Even 20 minutes of real effort makes the answer stick far better than reading it.
Marking the problem solved, or opening every hint, also shows it.
the aha
Key insight
patternStack & queue design
Same ring buffer as LC 622, plus moving head left: head = (head − 1 + k) % k. The + k keeps the index non-negative because Java's % can return negatives.
solutions
From brute force to optimal
Insert/remove at index 0 for the front, at the end for the back.
- insertFront:
add(0, v). - deleteFront:
remove(0). - Back operations at the end of the list.
Index-0 inserts/removes shift every element.
At most k elements.
// On LeetCode, name this class MyCircularDeque.class MyCircularDequeList { private final List<Integer> items = new ArrayList<>(); private final int capacity; public MyCircularDequeList(int k) { capacity = k; } public boolean insertFront(int value) { if (isFull()) return false; items.add(0, value); // shifts everything right: O(n) return true; } public boolean insertLast(int value) { if (isFull()) return false; items.add(value); return true; } public boolean deleteFront() { if (isEmpty()) return false; items.remove(0); // shifts everything left: O(n) return true; } public boolean deleteLast() { if (isEmpty()) return false; items.remove(items.size() - 1); return true; } public int getFront() { return isEmpty() ? -1 : items.get(0); } public int getRear() { return isEmpty() ? -1 : items.get(items.size() - 1); } public boolean isEmpty() { return items.isEmpty(); } public boolean isFull() { return items.size() == capacity; }}Ring buffer where head can step both ways; the rear is derived from head + size.
- insertFront:
head = (head − 1 + k) % k, write, size++. - insertLast: write at
(head + size) % k, size++. - deleteFront:
head = (head + 1) % k, size--. deleteLast: size--.
Constant index arithmetic, no shifting.
One fixed array.
class MyCircularDeque { private final int[] data; private int head = 0; // index of the front element private int size = 0; public MyCircularDeque(int k) { data = new int[k]; } public boolean insertFront(int value) { if (isFull()) return false; head = (head - 1 + data.length) % data.length; // step left; +length avoids a negative index data[head] = value; size++; return true; } public boolean insertLast(int value) { if (isFull()) return false; data[(head + size) % data.length] = value; size++; return true; } public boolean deleteFront() { if (isEmpty()) return false; head = (head + 1) % data.length; size--; return true; } public boolean deleteLast() { if (isEmpty()) return false; size--; // the rear is defined by head + size, so shrinking size is enough return true; } public int getFront() { return isEmpty() ? -1 : data[head]; } public int getRear() { return isEmpty() ? -1 : data[(head + size - 1) % data.length]; } public boolean isEmpty() { return size == 0; } public boolean isFull() { return size == data.length; }}Let the library do the ring buffer; only enforce the capacity.
- Reject inserts when size == k.
- Delegate to offerFirst/offerLast/pollFirst/pollLast/peekFirst/peekLast.
ArrayDeque end operations are amortized O(1).
At most k stored elements.
// On LeetCode, name this class MyCircularDeque.class MyCircularDequeLibrary { private final Deque<Integer> dq = new ArrayDeque<>(); private final int capacity; public MyCircularDequeLibrary(int k) { capacity = k; } public boolean insertFront(int value) { return !isFull() && dq.offerFirst(value); } public boolean insertLast(int value) { return !isFull() && dq.offerLast(value); } public boolean deleteFront() { return dq.pollFirst() != null; } public boolean deleteLast() { return dq.pollLast() != null; } public int getFront() { return dq.isEmpty() ? -1 : dq.peekFirst(); } public int getRear() { return dq.isEmpty() ? -1 : dq.peekLast(); } public boolean isEmpty() { return dq.isEmpty(); } public boolean isFull() { return dq.size() == capacity; }}in the room
How to say it in the interview
talk track
- Interviewers want the raw ring buffer; mention the ArrayDeque wrapper to show you know the library, then implement it by hand.
traps
(head − 1) % kis −1 when head is 0 in Java.
what they ask next
Follow-ups
Why keep size instead of only head and tail?
With just two indices, a full deque and an empty one both have head == tail. A size counter (or one wasted slot) tells them apart.
Make it grow instead of rejecting inserts when full?
Allocate an array twice as big and copy the elements in order, starting from head, so the new head is 0. Each insert is then amortized O(1) — this is what ArrayDeque does.
before you submit
Edge cases to test
- insertFront on an empty deque with head = 0: head must wrap to k − 1, never −1.
- k = 1: front and rear are the same slot.
- getFront and getRear on an empty deque return −1.
practise next
Similar problems
- #622Design Circular QueueMediumin Queues
- #239Sliding Window MaximumHardin Queues