We need to build a double-ended queue with a fixed maximum capacity, and it must wrap around, meaning the underlying storage behaves like a ring. Unlike a regular queue where you only add at one end and remove from the other, a deque lets you insert and delete from both the front and the rear.
The "circular" part is the real design challenge. When the front pointer reaches the beginning of the array, it should wrap to the end. When the rear pointer reaches the end, it should wrap to the beginning. This way, we never waste space from previous deletions, as long as the deque is not full.
Modular arithmetic handles the wrap-around. (index + 1) % capacity moves a pointer forward and (index - 1 + capacity) % capacity moves it backward, and neither expression ever goes out of bounds.
1 <= k <= 1000 → The deque capacity is small. Any reasonable approach works performance-wise.0 <= value <= 1000 → Values are non-negative integers, so returning -1 as a sentinel for "empty" is safe and unambiguous.2000 calls → Small enough that even O(n) shifting would pass, but the point of the problem is to support every operation in O(1), and both approaches below do.A fixed-size array with two pointers, front and rear, implements the deque directly. When a pointer moves past the end of the array, the modulo operator wraps it back to the beginning. When it moves before the beginning, it wraps to the end.
The convention matters: front points to the current front element, and rear points to the position after the last element (the next free slot at the back). A separate size counter tells us whether the deque is empty or full.
The size counter is not optional bookkeeping. With this convention, rear sits exactly size steps ahead of front (mod capacity), so front == rear occurs in two different situations: when the deque is empty and when the pointers have wrapped all the way around to meet at full capacity. The pointers alone cannot distinguish those cases. Tracking size resolves the ambiguity.
A size counter is not the only way to break the front == rear ambiguity. The classic alternative allocates k + 1 slots and treats the deque as full when (rear + 1) % (k + 1) == front, deliberately wasting one slot so that empty (front == rear) and full never look the same. It works, but every modulus in the code becomes k + 1 instead of k, and the wasted slot buys nothing that a size counter does not provide. The size counter is the simpler convention.
k and initialize front = 0, rear = 0, size = 0.front backward by one (wrapping with modulo), place the value at front, increment size.rear, then move rear forward by one (wrapping), increment size.front forward by one (wrapping), decrement size.rear backward by one (wrapping), decrement size.front or at (rear - 1 + capacity) % capacity.The circular array packs everything into one allocation, but it asks you to keep three separate (x - 1 + capacity) % capacity expressions consistent. A doubly linked list reaches the same O(1) bounds with no index arithmetic at all.
A doubly linked list stores each element in a node with prev and next pointers. Inserting at the front adds a node before the head, inserting at the back adds a node after the tail, and deleting from either end relinks two pointers. A size counter enforces the capacity limit, since the list itself has no built-in bound.
The trade against the circular array: each element costs a heap allocation plus two pointers of overhead, and the nodes are scattered in memory instead of packed in one cache-friendly block. In exchange, there is no modular arithmetic to get wrong, and the empty-versus-full ambiguity from Approach 1 disappears entirely. An empty deque is head == null, a full one is size == capacity, and the two conditions can never be confused.
Node class with val, prev, and next fields.head = null, tail = null, size = 0, capacity = k.head.next and set the new head's prev to null. Decrement size.tail.prev and set the new tail's next to null. Decrement size.head.val or tail.val, or -1 if empty.