Enqueue Using Linked List in DSA Rust - Time & Space Complexity
We want to understand how the time to add an item to a linked list queue grows as the queue gets bigger.
How does the number of steps change when we add more items?
Analyze the time complexity of the following code snippet.
struct Node {
value: i32,
next: Option>,
}
struct Queue {
head: Option>,
tail: Option<*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 let Some(tail) = self.tail {
unsafe {
(*tail).next = Some(new_node);
}
self.tail = Some(raw_node);
} else {
self.head = Some(new_node);
self.tail = Some(raw_node);
}
}
}
This code adds a new item to the end of a linked list queue by updating the tail pointer.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Adding a new node by updating pointers.
- How many times: This happens once per enqueue call, no loops or traversals.
Each time we add an item, the steps stay about the same because we keep track of the tail.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | ~1 pointer update |
| 100 | ~1 pointer update |
| 1000 | ~1 pointer update |
Pattern observation: The work to add one item does not grow with the queue size.
Time Complexity: O(1)
This means adding an item takes the same small amount of time no matter how many items are already in the queue.
[X] Wrong: "Adding an item takes longer as the queue grows because we must find the end each time."
[OK] Correct: We keep a pointer to the tail, so we add directly without searching, keeping time constant.
Knowing that enqueue in a linked list queue is fast and constant time shows you understand efficient data structure design, a key skill in coding interviews.
"What if we did not keep a tail pointer and had to find the end each time? How would the time complexity change?"
Practice
enqueue operation do in a linked list implementation of a queue?Solution
Step 1: Understand enqueue operation in queue
Enqueue means adding an element to the queue, which is done at the end (tail) in a linked list.Step 2: Identify linked list position for enqueue
In a linked list queue, new nodes are added at the tail to maintain FIFO order.Final Answer:
Adds a new element at the end (tail) of the linked list -> Option CQuick Check:
Enqueue = Add at tail [OK]
- Confusing enqueue with dequeue operation
- Adding new element at head instead of tail
- Thinking enqueue removes elements
val into a linked list queue?Solution
Step 1: Create new node with value and no next
The new node is created with data = val and next = None, correct for enqueue at tail.Step 2: Link new node at tail and update tail pointer
Setting tail.next to new_node and moving tail pointer to new_node is correct enqueue logic.Final Answer:
let new_node = Box::new(Node { data: val, next: None }); tail.next = Some(new_node); tail = tail.next.as_mut().unwrap(); -> Option AQuick Check:
Enqueue = add at tail and update tail [OK]
- Assigning new node to head instead of tail
- Not updating tail pointer after enqueue
- Linking new node's next to head (wrong for enqueue)
struct Node {
data: i32,
next: Option,
}
struct Queue {
head: Option,
tail: *mut Node,
}
impl Queue {
fn new() -> Self {
Queue { head: None, tail: std::ptr::null_mut() }
}
fn enqueue(&mut self, val: i32) {
let mut new_node = Box::new(Node { data: val, next: None });
let raw_node: *mut _ = &mut *new_node;
if self.tail.is_null() {
self.head = Some(new_node);
self.tail = raw_node;
} else {
unsafe { (*self.tail).next = Some(new_node); }
self.tail = raw_node;
}
}
fn print_queue(&self) {
let mut current = &self.head;
while let Some(node) = current {
print!("{} -> ", node.data);
current = &node.next;
}
println!("null");
}
}
fn main() {
let mut q = Queue::new();
q.enqueue(10);
q.enqueue(20);
q.enqueue(30);
q.print_queue();
}Solution
Step 1: Trace enqueue calls adding nodes at tail
Enqueue adds 10, then 20, then 30 at the tail, preserving order.Step 2: Print queue from head to tail
Prints nodes in order: 10 -> 20 -> 30 -> null.Final Answer:
10 -> 20 -> 30 -> null -> Option BQuick Check:
Enqueue order preserved in print [OK]
- Assuming enqueue adds at head, reversing order
- Not updating tail pointer causing print errors
- Printing empty queue due to wrong head initialization
fn enqueue(&mut self, val: i32) {
let new_node = Box::new(Node { data: val, next: None });
if self.tail.is_null() {
self.head = Some(new_node);
self.tail = &mut *new_node;
} else {
unsafe { (*self.tail).next = Some(new_node); }
self.tail = &mut *new_node;
}
}Solution
Step 1: Analyze new_node lifetime
new_node is a local variable and will be dropped after function ends, so tail pointer becomes invalid.Step 2: Understand pointer assignment issue
Assigning tail to &mut *new_node stores pointer to dropped memory, causing undefined behavior.Final Answer:
Assigning tail pointer to reference of new_node which is dropped after function ends -> Option DQuick Check:
Pointer to dropped node causes bug [OK]
- Using local variable reference for tail pointer
- Ignoring unsafe pointer lifetime issues
- Not updating tail pointer at all
Option for next pointers.
C) Add new node at tail but do not update tail pointer.
D) Add new node at head and update tail pointer.Solution
Step 1: Understand enqueue efficiency in linked list queue
Enqueue must add at tail and update tail pointer to keep O(1) time complexity.Step 2: Use Option for safe memory management
Using Option for next pointers ensures ownership and safe heap allocation in Rust.Step 3: Reject other options
Adding at head breaks FIFO order; not updating tail pointer breaks enqueue logic.Final Answer:
Add new node at tail, update tail pointer, and use Option for next pointers. -> Option AQuick Check:
Enqueue at tail + safe pointers = O(1) enqueue [OK]
- Adding at head breaks queue order
- Not updating tail pointer after enqueue
- Using raw pointers without ownership
Ready to prove it?
Pass the test and this pattern moves unknown
