Remove Nth Node from End of List in DSA Rust - Time & Space Complexity
We want to understand how the time to remove the nth node from the end of a list changes as the list grows.
How does the number of steps grow when the list gets longer?
Analyze the time complexity of the following code snippet.
fn remove_nth_from_end(head: Option>, n: i32) -> Option> {
let mut dummy = Box::new(ListNode { val: 0, next: head });
let mut second = &mut dummy;
let mut first = &*second;
for _ in 0..n {
first = first.next.as_ref().unwrap();
}
first = first.next.as_ref().unwrap();
while first.next.is_some() {
first = first.next.as_ref().unwrap();
second = second.next.as_mut().unwrap();
}
let next = second.next.as_mut().unwrap().next.take();
second.next = next;
dummy.next
}
This code removes the nth node from the end of a singly linked list by using two pointers moving through the list.
- Primary operation: Two loops that move pointers through the list.
- How many times: The first loop moves n steps; the second loop moves through the rest of the list until the end.
As the list size grows, the total steps grow roughly in a straight line with the list length.
| Input Size (n) | Approx. Operations |
|---|---|
| 10 | About 10 steps |
| 100 | About 100 steps |
| 1000 | About 1000 steps |
Pattern observation: The number of steps grows directly with the list size, so doubling the list roughly doubles the work.
Time Complexity: O(n)
This means the time to remove the nth node from the end grows linearly with the list size.
[X] Wrong: "The operation takes constant time because we only remove one node."
[OK] Correct: Even though only one node is removed, we must first find it by walking through the list, which takes time proportional to the list length.
Understanding this linear time complexity helps you explain why two-pointer techniques are efficient for linked list problems and shows your grasp of pointer traversal skills.
"What if we used a doubly linked list with a tail pointer? How would the time complexity change?"
Practice
Remove Nth Node from End of List ask you to do?Solution
Step 1: Understand the problem statement
The problem requires removing the node that is nth from the end in a singly linked list.Step 2: Clarify the operation
We do not add or reverse nodes; we specifically delete the nth node counting from the list's end.Final Answer:
Delete the node that is the nth from the end in a linked list -> Option CQuick Check:
Remove nth from end = Delete nth from end [OK]
- Confusing nth from end with nth from start
- Thinking the problem is about reversing the list
- Assuming insertion instead of deletion
Solution
Step 1: Recall Rust ownership for linked lists
Rust usesOptionto allow nullable next pointers with ownership.Step 2: Check each option
struct ListNode { val: i32, next: Option } usesOptionwhich is correct. Options A and D use references without ownership, which is unsafe. struct ListNode { val: i32, next: Box<ListNode> } lacks Option, so no null end.Final Answer:
struct ListNode { val: i32, next: Option } -> Option BQuick Check:
Rust linked list node = Option [OK]
- Using references (&) instead of Box for next
- Omitting Option causing no null end
- Forgetting ownership rules in Rust
let mut list = Some(Box::new(ListNode { val: 1, next: Some(Box::new(ListNode { val: 2, next: Some(Box::new(ListNode { val: 3, next: None })) })) }));
// Remove 2nd from end
let result = remove_nth_from_end(list, 2);
// Print list valuesSolution
Step 1: Identify the list and node to remove
The list is 1 -> 2 -> 3. Removing 2nd from end means removing node with value 2.Step 2: Remove the node and update links
After removal, list becomes 1 -> 3 -> null.Final Answer:
1 -> 3 -> null -> Option DQuick Check:
Remove 2nd from end removes '2' [OK]
- Removing the wrong node counting from start
- Not updating next pointers correctly
- Confusing 2nd from end with 2nd from start
fn remove_nth_from_end(head: Option, n: i32) -> Option {
let mut dummy = Box::new(ListNode { val: 0, next: head });
let mut first = &dummy;
let mut second = &mut dummy;
for _ in 0..n {
first = first.next.as_ref().unwrap();
}
while first.next.is_some() {
first = first.next.as_ref().unwrap();
second = second.next.as_mut().unwrap();
}
second.next = second.next.as_mut().unwrap().next.take();
dummy.next
}Solution
Step 1: Analyze unwrap usage
The code uses unwrap() on next without checking if next exists, which can panic if n is larger than list length.Step 2: Confirm other references are valid
first is immutable reference but used only for reading, second is mutable for modification, dummy.next returned after modification.Final Answer:
Using unwrap() without checking may panic if list is short -> Option AQuick Check:
unwrap() without safety check causes panic [OK]
- Confusing mutable and immutable references
- Ignoring unwrap panic risks
- Assuming dummy.next is returned too early
Solution
Step 1: Understand the edge case
If the node to remove is the head, removing it directly is tricky because head pointer must change.Step 2: Use dummy node technique
Adding a dummy node before head simplifies removal by always having a previous node to update, including when head is removed.Final Answer:
Use a dummy node before head and adjust dummy.next after removal -> Option AQuick Check:
Dummy node handles head removal edge case [OK]
- Not handling head removal separately
- Forgetting to update head pointer
- Using inefficient vector conversion
Ready to prove it?
Pass the test and this pattern moves unknown
