How do you remove the nth node from the end of a linked list in a single pass?
Use two pointers separated by exactly n nodes. Advance the fast pointer n steps, then move both pointers together until the fast one reaches the end. At that point the slow pointer sits on the node before the one you want to remove.
class Node:
def __init__(self, value, next_node=None):
self.value = value
self.next = next_node
def build(values):
head = None
for value in reversed(values):
head = Node(value, head)
return head
def to_list(head):
out = []
while head:
out.append(head.value)
head = head.next
return out
def remove_nth(head, n):
dummy = Node(0, head)
fast = slow = dummy
for _ in range(n):
fast = fast.next
while fast:
fast = fast.next
slow = slow.next
slow.next = slow.next.next
return dummy.next
print(to_list(remove_nth(build([1, 2, 3, 4, 5]), 2)))
print(to_list(remove_nth(build([1, 2, 3, 4, 5]), 5)))The outputs are 1, 2, 3, 5 and 2, 3, 4, 5. Removing the second from the end drops the 4, and removing the fifth from the end of a five element list drops the first element.
The mechanism is that the gap between the pointers is what identifies the target. When the fast pointer has walked off the end, exactly n nodes separate it from the slow pointer, and the node the slow pointer points at is the one n positions from the end, which is the node before the one to remove. Unlinking it is then a single assignment to the previous node's next pointer.
The trade-off to name is that this needs the length, which a two pointer approach gets for free. The obvious alternative counts the nodes in a first pass, computes the index from the end, and walks again, which is two passes but easier to follow. The dummy node is what makes the single pass version uniform: without it, removing the head would need a separate branch, because there is no previous node to relink. The version above also assumes n is valid, so a guard for an out of range n belongs in production code.