Dequeue Using Linked List in DSA Rust - Time & Space Complexity
We want to understand how fast operations on a dequeue using a linked list run.
Specifically, how the time changes when we add or remove items.
Analyze the time complexity of the following code snippet.
struct Node {
value: i32,
next: Option>,
prev: Option<*mut Node>,
}
struct Dequeue {
head: Option>,
tail: Option<*mut Node>,
}
impl Dequeue {
fn push_front(&mut self, val: i32) {
// Add node at front
}
fn pop_back(&mut self) -> Option {
// Remove node from back
}
}
This code shows a double-ended queue using a linked list where we add at front and remove from back.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Adding or removing a node at the ends of the linked list.
- How many times: Each operation works on just one node at a time, no loops or traversals.
Each add or remove touches only the ends, so time does not grow with more items.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | 1 |
| 100 | 1 |
| 1000 | 1 |
Pattern observation: Time stays the same no matter how many items are in the dequeue.
Time Complexity: O(1)
This means adding or removing from either end takes the same short time regardless of size.
[X] Wrong: "Removing from the back takes longer because you have to find the last node."
[OK] Correct: The linked list keeps a direct pointer to the tail, so no searching is needed.
Knowing that dequeue operations run in constant time shows you understand efficient data structures.
"What if we used a singly linked list without a tail pointer? How would the time complexity change?"
Practice
Solution
Step 1: Understand dequeue operations
A dequeue allows insertion and deletion at both front and rear ends.Step 2: Compare linked list and array
Linked list supports these operations in constant time without shifting elements, unlike arrays.Final Answer:
It allows insertion and deletion from both ends in constant time without shifting elements. -> Option CQuick Check:
Linked list dequeue = fast insert/delete both ends [OK]
- Thinking arrays handle front insertions efficiently
- Assuming linked list sorts elements automatically
- Confusing memory usage advantages
Solution
Step 1: Understand Rust ownership for linked lists
Rust needs smart pointers like Rc and RefCell for shared mutable ownership in doubly linked lists.Step 2: Analyze options
struct Node<T> { data: T, prev: Option>>, next: Option>> } uses Rc> for prev and next, allowing multiple owners and mutability safely.Final Answer:
struct Node<T> { data: T, prev: Option>>, next: Option>> } -> Option BQuick Check:
Use Rc<RefCell> for safe shared mutable nodes [OK]
- Using Box alone causes ownership errors
- Using raw pointers without unsafe blocks
- Mixing Box and Rc incorrectly
let mut dq = Dequeue::new(); dq.push_front(10); dq.push_rear(20); dq.push_front(5); dq.pop_rear(); dq.display();
Solution
Step 1: Trace insertions
push_front(10) adds 10 at front: 10 -> null; push_rear(20) adds 20 at rear: 10 -> 20 -> null; push_front(5) adds 5 at front: 5 -> 10 -> 20 -> null.Step 2: Trace pop_rear and display
pop_rear() removes 20 from rear: 5 -> 10 -> null; display prints this sequence.Final Answer:
5 -> 10 -> null -> Option DQuick Check:
After pop_rear, rear 20 removed, list is 5 -> 10 [OK]
- Confusing push_front and push_rear order
- Forgetting pop_rear removes last element
- Printing outdated list state
fn pop_front(&mut self) -> Option<T> {
let node = self.head.take().unwrap();
self.head = node.next;
Some(node.data)
}Solution
Step 1: Analyze unwrap() usage
unwrap() on self.head.take() will panic if self.head is null (empty dequeue).Step 2: Understand runtime panic cause
If dequeue is empty, calling pop_front causes unwrap() on null, triggering panic.Final Answer:
Calling unwrap() on null when dequeue is empty. -> Option AQuick Check:
unwrap() on null causes panic [OK]
- Ignoring empty dequeue before unwrap()
- Assuming node.next is not Option
- Forgetting to update tail pointer
Solution
Step 1: Choose smart pointers for shared mutable ownership
Rc> allows multiple owners and interior mutability needed for doubly linked list nodes.Step 2: Implement Iterator trait for front-to-rear traversal
Implementing Iterator trait enables easy iteration over dequeue elements from front to rear.Final Answer:
Use Rc> for nodes and implement Iterator trait for traversal. -> Option AQuick Check:
Rc<RefCell> + Iterator = safe mutable nodes + easy traversal [OK]
- Using Box alone prevents multiple ownership
- Raw pointers require unsafe code
- Arc<Mutex> is for concurrency, not needed here
Ready to prove it?
Pass the test and this pattern moves unknown
