Design Circular Queue
LeetCode 860 | Difficulty: Mediumβ
MediumProblem 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.
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.
In-place list manipulation, cycle detection, merging lists, finding the k-th node.
Solutionsβ
Solution 1: C# (Best: 136 ms)β
| Metric | Value |
|---|---|
| Runtime | 136 ms |
| Memory | 34 MB |
| Date | 2019-12-16 |
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β
| Approach | Time | Space |
|---|---|---|
| Linked List | $O(n)$ | $O(1)$ |
Interview Tipsβ
- 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.