Remove Nth Node From End of List
Given the head of a linked list, remove the nth node from the end of the list and return its head.
- The number of nodes in the list is sz.
- 1 <= sz <= 30
- 0 <= Node.val <= 100
- 1 <= n <= sz
head = [1,2,3,4,5], n = 2[1,2,3,5]head = [1], n = 1[]head = [1,2], n = 2[2]We want to remove the N-th node from the very end of a singly linked list. But a linked list is a one-way chain of memory addresses; we cannot jump directly to a specific position, nor do we know its total length without walking it from start to finish. Finding a target from the front is simple, but how do we target a node relative to the back when we cannot look backward or see the end ahead of time?
The most straightforward approach is to solve this in two separate trips. First, we walk the entire list to count how many nodes exist. Once we have the total length, we calculate the predecessor index of our target (Length - N). Then, we reset our pointer and take a second trip, walking exactly to that predecessor node and rewiring its pointer to skip the target.
While this works, it forces us to make two full passes over the list. Making two trips when we only need to delete a single node is a waste of execution time.
# Trip 1: Count total nodes
length = 0
curr = head
while curr:
length += 1
curr = curr.next
# Trip 2: Walk to the predecessor
target_idx = length - n
curr = head
for _ in range(target_idx - 1):
curr = curr.next
# Rewire pointer to skip the target node
curr.next = curr.next.nextTo delete the node in a single pass, we can use two pointers separated by a fixed distance. Each pointer has a strict role:
- The Scout (scout): Sprints ahead to establish a physical boundary gap.
- The Shadow (shadow): Follows at the exact same pace, positioned exactly N+1 steps behind the scout.
Both start parked at the very front of our list.
If the list has 5 nodes and we are asked to remove the 5th node from the end, we are actually removing the very first node (the head). In a naive implementation, removing the head requires unique code because it has no predecessor.
To unify our logic and avoid crashes, we attach a fake Dummy Node to the very front of the list, pointing to the original head. By starting both the scout and shadow at this dummy sentinel, every physical node—including the first one—is guaranteed to have a predecessor (shadow) to safely rewire its link.
First, we tell the scout to advance exactly N+1 steps while the shadow remains stationary at the dummy node. This creates a perfect gap.
Once the gap is established, we advance both pointers one step at a time. Because the scout is exactly N+1 steps ahead, the exact moment the scout falls off the end of the list (becoming null), the shadow will land exactly one node before the target. We then perform the pointer surgery, bypassing the target node completely.
# Initialize dummy sentinel
dummy = Node(0, head)
scout = dummy
shadow = dummy
# 1. Establish the N + 1 node gap
for _ in range(n + 1):
scout = scout.next
# 2. Advance both pointers in unison
while scout:
scout = scout.next
shadow = shadow.next
# 3. Surgery: Bypass the target node
shadow.next = shadow.next.next
# Return the new head (skipping dummy)
return dummy.nextThe sentinel-guided approach handles edge conditions with ease:
- Snipping the Head (N = Length): The scout moves all the way to null in the first step. The shadow stays at the dummy node. The bypass step rewires dummy.next = dummy.next.next, removing the original head seamlessly.
- Single-Node List (N = 1, length = 1): scout moves 2 steps to null. shadow stays at dummy. We bypass Node 1 and return null.
N-th Node Coordinate Mapping
Operation Status
The Silent End
We want to remove the N-th node from the end. But in a singly linked list, we can't see the finish line until we cross it. How do we target the unseen?
When the Fast pointer is N steps ahead, it "drags" the Slow pointer exactly N nodes behind it. Hits the end, found the target.
To handle edge cases (like removing the head), always start with a **Dummy** node pointing to the head.