Skip to main content

Design Circular Queue

LeetCode 860 | Difficulty: Medium​

Medium

Problem Description​

Design your implementation of the 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.

You must solve the problem without using the built-in queue data structure in your programming language.

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 <= 1000`

- At most `3000` calls will be made to `enQueue`, `deQueue`, `Front`, `Rear`, `isEmpty`, and `isFull`.

Topics: Array, Linked List, Design, Queue


Approach​

Design​

Choose the right data structures to meet the required time complexities for each operation. Consider hash maps for O(1) access, doubly-linked lists for O(1) insertion/deletion, and combining structures for complex requirements.

When to use

Implementing a data structure or system with specific operation time requirements.

Linked List​

Use pointer manipulation. Common techniques: dummy head node to simplify edge cases, fast/slow pointers for cycle detection and middle finding, prev/curr/next pattern for reversal.

When to use

In-place list manipulation, cycle detection, merging lists, finding the k-th node.


Solutions​

Solution 1: C# (Best: 136 ms)​

MetricValue
Runtime136 ms
Memory34 MB
Date2019-12-16
Solution
public class MyCircularQueue {

public int[] Q;
public int Head;
public int Tail;
public int Count;
private readonly int Size;

/** Initialize your data structure here. Set the size of the queue to be k. */
public MyCircularQueue(int k)
{
Q = new int[k];
Head = 0;
Tail = -1;
Count = 0;
Size = k;
}

/** Insert an element into the circular queue. Return true if the operation is successful. */
public bool EnQueue(int value)
{
if(IsFull()) return false;
Tail = ++Tail%Size;
Q[Tail] = value;
Count++;
return true;
}

/** Delete an element from the circular queue. Return true if the operation is successful. */
public bool DeQueue()
{
if(IsEmpty()) return false;
Head = ++Head%Size;
Count--;
return true;

}

/** Get the front item from the queue. */
public int Front()
{
return IsEmpty() ? -1 : Q[Head];
}

/** Get the last item from the queue. */
public int Rear()
{
return IsEmpty() ? -1 : Q[Tail];
}

/** Checks whether the circular queue is empty or not. */
public bool IsEmpty()
{
return Count == 0;
}

/** Checks whether the circular queue is full or not. */
public bool IsFull()
{
return Count == Q.Length;
}
}

/**
* Your MyCircularQueue object will be instantiated and called as such:
* MyCircularQueue obj = new MyCircularQueue(k);
* bool param_1 = obj.EnQueue(value);
* bool param_2 = obj.DeQueue();
* int param_3 = obj.Front();
* int param_4 = obj.Rear();
* bool param_5 = obj.IsEmpty();
* bool param_6 = obj.IsFull();
*/
πŸ“œ 1 more C# submission(s)

Submission (2019-12-16) β€” 136 ms, 34 MB​

public class MyCircularQueue {

public int[] Q;
public int Head;
public int Tail;
public int Count;
private readonly int Size;

/** Initialize your data structure here. Set the size of the queue to be k. */
public MyCircularQueue(int k)
{
Q = new int[k];
Head = 0;
Tail = -1;
Count = 0;
Size = k;
}

/** Insert an element into the circular queue. Return true if the operation is successful. */
public bool EnQueue(int value)
{
if(IsFull()) return false;
Tail = ++Tail%Size;
Q[Tail] = value;
Count++;
return true;
}

/** Delete an element from the circular queue. Return true if the operation is successful. */
public bool DeQueue()
{
if(IsEmpty()) return false;
Head = ++Head%Size;
Count--;
return true;

}

/** Get the front item from the queue. */
public int Front()
{
return IsEmpty() ? -1 : Q[Head];
}

/** Get the last item from the queue. */
public int Rear()
{
return IsEmpty() ? -1 : Q[Tail];
}

/** Checks whether the circular queue is empty or not. */
public bool IsEmpty()
{
return Count == 0;
}

/** Checks whether the circular queue is full or not. */
public bool IsFull()
{
return Count == Q.Length;
}
}

/**
* Your MyCircularQueue object will be instantiated and called as such:
* MyCircularQueue obj = new MyCircularQueue(k);
* bool param_1 = obj.EnQueue(value);
* bool param_2 = obj.DeQueue();
* int param_3 = obj.Front();
* int param_4 = obj.Rear();
* bool param_5 = obj.IsEmpty();
* bool param_6 = obj.IsFull();
*/

Complexity Analysis​

ApproachTimeSpace
Linked List$O(n)$$O(1)$

Interview Tips​

Key Points
  • Discuss the brute force approach first, then optimize. Explain your thought process.
  • Draw the pointer changes before coding. A dummy head node simplifies edge cases.
  • Clarify the expected time complexity for each operation before choosing data structures.