Delete from End of Doubly Linked List in DSA Rust - Time & Space Complexity
We want to understand how long it takes to remove the last item from a doubly linked list.
How does the time needed change as the list grows bigger?
Analyze the time complexity of the following code snippet.
struct Node {
data: i32,
prev: Option<*mut Node>,
next: Option<Box<Node>>,
}
fn delete_from_end(head: &mut Option<Box<Node>>) {
if head.is_none() { return; }
if head.as_ref().unwrap().next.is_none() {
*head = None;
return;
}
let mut current = head.as_mut();
while let Some(node) = current {
if node.next.is_none() {
// Remove last node
if let Some(prev) = node.prev {
unsafe {
(*prev).next = None;
}
}
break;
}
current = node.next.as_mut();
}
}
This code removes the last node from a doubly linked list by walking to the end and unlinking it.
Identify the loops, recursion, array traversals that repeat.
- Primary operation: Loop that moves through nodes from head to end.
- How many times: Once for each node until the last one (depends on list size).
As the list gets longer, the code checks more nodes one by one until it reaches the end.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 steps to reach the end |
| 100 | About 100 steps to reach the end |
| 1000 | About 1000 steps to reach the end |
Pattern observation: The number of steps grows directly with the list size.
Time Complexity: O(n)
This means the time to delete the last node grows in a straight line with the number of nodes.
[X] Wrong: "Deleting the last node is always quick and takes constant time."
[OK] Correct: Without a direct pointer to the last node, we must walk through the list, so time depends on list length.
Understanding this helps you explain how linked lists work and why having a tail pointer can speed up some operations.
"What if we kept a pointer to the last node? How would the time complexity change?"
Practice
Solution
Step 1: Understand the tail node removal
When deleting the last node, the node before it becomes the new tail.Step 2: Update pointers
The new tail's next pointer must be set to null to mark the end of the list.Final Answer:
The second last node becomes the new tail and its next pointer is set to null -> Option AQuick Check:
Tail update = new tail with next = null [OK]
- Not updating the tail pointer
- Forgetting to set next pointer of new tail to null
- Deleting the head instead of the tail
Solution
Step 1: Check tail existence and update pointers
The code safely takes the tail node, then updates the previous node's next pointer to null.Step 2: Update tail or head accordingly
If previous node exists, tail is updated to it; else, list becomes empty by setting head to null.Final Answer:
Code snippet that safely updates tail and head pointers -> Option AQuick Check:
Safe pointer update with if let Some(tail) = self.tail.take() { if let Some(prev) = tail.prev.take() { prev.next = null; self.tail = Some(prev); } else { self.head = null; } } [OK]
- Directly assigning tail without checking Option
- Not updating previous node's next pointer
- Setting head to null without checking list length
let mut list = DoublyLinkedList::new(); list.push_back(1); list.push_back(2); list.push_back(3); list.delete_from_end(); list.print();
Solution
Step 1: Initial list state
The list contains nodes 1, 2, 3 in order.Step 2: Delete last node
Deleting from end removes node 3, so list becomes 1 -> 2.Final Answer:
1 -> 2 -> null -> Option BQuick Check:
After deletion, last node removed = 1 -> 2 [OK]
- Assuming head node is deleted
- Not updating tail pointer
- Printing old list without deletion
fn delete_from_end(&mut self) {
if self.tail.is_none() {
return;
}
let tail = self.tail.unwrap();
if let Some(prev) = tail.prev {
prev.next = null;
self.tail = Some(prev);
} else {
self.head = null;
self.tail = null;
}
}Solution
Step 1: Analyze unwrap() usage
Calling unwrap() on tail without checking can panic if tail is null.Step 2: Suggest safe handling
Use if let or take() to safely handle Option values without panic.Final Answer:
Using unwrap() on Option without safe handling causes panic -> Option CQuick Check:
Unsafe unwrap() = panic risk [OK]
- Ignoring Option safety and causing runtime panic
- Not updating tail pointer after deletion
- Forgetting to set prev.next to null
Solution
Step 1: Initial list nodes
The list starts as 10 -> 20 -> 30.Step 2: Delete last node once
Removing last node 30 results in 10 -> 20.Step 3: Delete last node again
Removing last node 20 results in 10.Final Answer:
10 -> null -> Option DQuick Check:
Two deletions remove last two nodes [OK]
- Deleting head instead of tail
- Not updating tail after each deletion
- Assuming list becomes empty too early
Ready to prove it?
Pass the test and this pattern moves unknown
