Problem
You're given the head of a linked list and an integer n. Remove the nth
node counting from the end of the list, in one pass, and return the (possibly
new) head.
Signal
"From the end, in one pass" without knowing the length up front is a fixed-gap
two-pointer job — open a gap of n nodes between two pointers, then slide
both forward together until the leading one runs out.
Approach
Attach a dummy node before head so removing the actual head is not a
special case. Advance a fast pointer n + 1 steps ahead of slow (both
starting at the dummy). Then advance both one step at a time until fast
falls off the end — at that point slow sits just before the node to remove,
so splice it out.
Skeleton
dummy = Node(next=head)
slow = fast = dummy
for _ in range(n + 1):
fast = fast.next
while fast:
slow, fast = slow.next, fast.next
slow.next = slow.next.next
return dummy.next
Solution
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def remove_nth_from_end(head: ListNode | None, n: int) -> ListNode | None:
dummy = ListNode(next=head)
slow = fast = dummy
for _ in range(n + 1):
fast = fast.next
while fast:
slow = slow.next
fast = fast.next
slow.next = slow.next.next
return dummy.next
Complexity
O(n) time — one pass with two pointers, no length precomputed separately. O(1) space.
Pitfalls
- Advancing
fastonlynsteps instead ofn + 1leavesslowsitting ON the target node instead of just before it, so you can't splice it out. - Skipping the dummy node and handling "remove the head" as a special case duplicates logic for no benefit — the dummy makes every removal uniform.
- A two-pass version (count length, then walk
length - nsteps) is also correct and arguably easier to explain — mention it, but the one-pass two-pointer version is what "without knowing the length" is asking for.