Queue Implementation Using Linked List in DSA Rust - Time & Space Complexity
We want to understand how fast the queue operations run when using a linked list.
Specifically, how the time to add or remove items changes as the queue grows.
Analyze the time complexity of the following code snippet.
struct Node {
value: i32,
next: Option>,
}
struct Queue {
front: Option>,
rear: *mut Node,
}
impl Queue {
fn enqueue(&mut self, val: i32) {
let mut new_node = Box::new(Node { value: val, next: None });
let raw_node: *mut _ = &mut *new_node;
if self.rear.is_null() {
self.front = Some(new_node);
self.rear = raw_node;
} else {
unsafe { (*self.rear).next = Some(new_node); }
self.rear = raw_node;
}
}
fn dequeue(&mut self) -> Option {
self.front.take().map(|node| {
self.front = node.next;
if self.front.is_none() {
self.rear = std::ptr::null_mut();
}
node.value
})
}
}
This code adds (enqueue) and removes (dequeue) items from a queue using a linked list.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Adding or removing one node at a time.
- How many times: Each enqueue or dequeue runs once per call, no loops inside.
Each operation works the same regardless of queue size.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | 1 operation per enqueue or dequeue |
| 100 | 1 operation per enqueue or dequeue |
| 1000 | 1 operation per enqueue or dequeue |
Pattern observation: The time to add or remove does not increase as the queue grows.
Time Complexity: O(1)
This means adding or removing an item takes the same short time no matter how many items are in the queue.
[X] Wrong: "Enqueue or dequeue takes longer as the queue gets bigger because it has to look through all nodes."
[OK] Correct: The linked list keeps track of the front and rear nodes, so it adds or removes directly without searching.
Knowing that queue operations run in constant time shows you understand efficient data structures, a key skill in coding interviews.
"What if we did not keep a pointer to the rear node? How would the time complexity of enqueue change?"
Practice
Queue using a linked list instead of an array?Solution
Step 1: Understand queue size behavior
Arrays have fixed size or require resizing, linked lists grow dynamically.Step 2: Compare memory and resizing
Linked lists allocate nodes as needed, so no fixed capacity limit exists.Final Answer:
Dynamic size without fixed capacity limit -> Option CQuick Check:
Linked list queues grow dynamically = D [OK]
- Thinking linked lists allow fast random access
- Assuming arrays use less memory always
- Confusing implementation simplicity
Solution
Step 1: Understand Rust ownership for linked lists
Rust requires heap allocation for recursive types using Box and Option for nullable links.Step 2: Analyze options
struct Node { data: i32, next: Option } uses Option which is correct for nullable next pointer; others cause infinite size or borrowing issues.Final Answer:
struct Node { data: i32, next: Option } -> Option AQuick Check:
Use Option for nullable next in Rust [OK]
- Using direct Node type causes infinite size error
- Using references (&Node) without lifetime causes errors
- Omitting Option for nullable next pointer
let mut queue = Queue::new();
queue.enqueue(10);
queue.enqueue(20);
let val = queue.dequeue();
println!("Dequeued: {:?}", val);
queue.print();Solution
Step 1: Trace enqueue operations
Enqueue 10 adds node with 10, then enqueue 20 adds node with 20 at the end.Step 2: Trace dequeue operation
Dequeue removes front node (10), returns Some(10). Remaining queue has 20 only.Final Answer:
Dequeued: Some(10) 20 -> null -> Option AQuick Check:
First in first out, dequeue returns 10, queue left with 20 [OK]
- Assuming dequeue removes last element
- Printing queue before dequeue
- Confusing Some and None return values
fn enqueue(&mut self, data: i32) {
let new_node = Box::new(Node { data, next: None });
if self.tail.is_none() {
self.head = Some(new_node);
} else {
self.tail.as_mut().unwrap().next = Some(new_node);
}
self.tail = Some(new_node);
}Solution
Step 1: Analyze ownership of new_node
new_node is moved into head or tail.next, so cannot be moved again.Step 2: Identify use-after-move error
Assigning self.tail = Some(new_node) after moving new_node causes compile error.Final Answer:
Assigning new_node to tail after moving it causes use-after-move error -> Option DQuick Check:
Moving new_node twice causes error [OK]
- Ignoring Rust ownership rules
- Not updating tail pointer correctly
- Confusing Box and Rc usage
peek for your linked list queue in Rust that returns the front element without removing it. Which approach is correct?Solution
Step 1: Understand peek method goal
Peek returns front element without removing it, so no ownership transfer.Step 2: Choose correct return type
Borrowing data with Option<&i32> avoids cloning or moving, safe and efficient.Final Answer:
Return Option<&i32> by borrowing data from head node -> Option BQuick Check:
Peek borrows front data, no ownership change [OK]
- Moving head node causing queue modification
- Returning owned data causing unnecessary clone
- Panicking instead of returning Option
Ready to prove it?
Pass the test and this pattern moves unknown
