Loading problems...
TL;DR: Use two pointers with a fixed separation of nodes to locate the node preceding the target in a single pass, enabling efficient removal.
The LeetCode 19: Remove Nth Node From End of List problem asks us to delete the -th node from the end of a singly linked list and return the head of the modified list. The challenge lies in identifying the target node's position relative to the end of the list, rather than the beginning, particularly when the total length of the list is unknown. The optimal solution for interviews requires achieving this in a single pass.
The naive approach to solving this problem involves a two-pass strategy. Since we do not know the length of the linked list initially, we cannot immediately determine which node is the -th from the end.
next pointer of the predecessor to skip the target node.Pseudo-code:
textfunction removeNthFromEnd(head, n): length = 0 current = head while current is not null: length++ current = current.next targetIndex = length - n if targetIndex == 0: return head.next current = head for i from 0 to targetIndex - 1: current = current.next current.next = current.next.next return head
Why it is suboptimal: While the time complexity is and space is , this approach requires traversing the list twice (specifically steps). The problem statement includes a follow-up asking for a one-pass solution. In high-performance scenarios or interviews, reducing the number of traversals demonstrates a deeper understanding of pointer manipulation.
The core intuition behind the optimal LeetCode 19 solution is to create a "window" or "gap" of size between two pointers, fast and slow.
If we advance the fast pointer so that it is steps ahead of the slow pointer, the two pointers maintain this fixed distance as they move. When the fast pointer reaches the end of the list (null), the slow pointer will be exactly nodes away from the end.
To delete a node in a singly linked list, we need access to its predecessor (the node immediately before it). Therefore, we adjust the gap slightly: we ensure the fast pointer is steps ahead of the slow pointer.
Visual Description:
Imagine two pointers moving along the list. Initially, both start at a dummy node placed before the head. The fast pointer moves forward times, creating the gap. Then, both pointers move one step at a time simultaneously. As fast falls off the end of the list (reaches null), slow lands exactly on the node before the one we want to remove. This allows us to perform the deletion operation slow.next = slow.next.next immediately.

Let's trace the algorithm with Input: head = [1, 2, 3, 4, 5], n = 2.
Setup:
dummy node with val=0, next=head.dummy -> 1 -> 2 -> 3 -> 4 -> 5 -> null.slow points to dummy.fast points to dummy.Create Gap:
fast (which is 3) steps.fast at 1.fast at 2.fast at 3.slow at dummy, fast at 3. Gap is established.Slide Window:
fast is not null.slow moves to 1.fast moves to 4.slow moves to 2.fast moves to 5.slow moves to 3.fast moves to .Deletion:
slow is at node 3. The target to remove is slow.next (node 4).slow.next = slow.next.next (points 3 to 5).dummy -> 1 -> 2 -> 3 -> 5 -> null.Return:
dummy.next (node 1).Let be the length of the list. The nodes are indexed to . The -th node from the end corresponds to the index . The predecessor of this node is at index .
We start with a dummy node at index .
fast by steps. fast is now at index (relative to the original list, or steps from dummy).fast to the end (null, effectively index ) is steps? No, simpler: fast starts at dummy. After steps, fast is at the -th node of the list.fast takes to reach null from its current position.slow also takes steps from the dummy node.fast reaches null, it has traversed a total of nodes (including dummy).slow is steps behind fast.slow's position is steps from the start (including dummy).slow lands on index , which is exactly the predecessor of the target node at .cpp/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode() : val(0), next(nullptr) {} * ListNode(int x) : val(x), next(nullptr) {} * ListNode(int x, ListNode *next) : val(x), next(next) {} * }; */ class Solution { public: ListNode* removeNthFromEnd(ListNode* head, int n) { // Use a dummy node to handle edge cases like removing the head ListNode* dummy = new ListNode(0); dummy->next = head; ListNode* slow = dummy; ListNode* fast = dummy; // Advance fast pointer so that the gap between fast and slow is n nodes // We move n + 1 times to land slow on the predecessor of the target for (int i = 0; i <= n; ++i) { fast = fast->next; } // Move both pointers until fast reaches the end while (fast != nullptr) { slow = slow->next; fast = fast->next; } // Remove the nth node from the end ListNode* nodeToDelete = slow->next; slow->next = slow->next->next; delete nodeToDelete; // Good practice in C++ to free memory ListNode* newHead = dummy->next; delete dummy; // Clean up dummy node return newHead; } };
java/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ class Solution { public ListNode removeNthFromEnd(ListNode head, int n) { // Dummy node simplifies edge cases (e.g., removing the only node) ListNode dummy = new ListNode(0); dummy.next = head; ListNode slow = dummy; ListNode fast = dummy; // Move fast pointer n + 1 steps ahead for (int i = 0; i <= n; i++) { fast = fast.next; } // Move both pointers until fast reaches the end while (fast != null) { slow = slow.next; fast = fast.next; } // Skip the desired node slow.next = slow.next.next; return dummy.next; } }
python# Definition for singly-linked list. # class ListNode: # def __init__(self, val=0, next=None): # self.val = val # self.next = next class Solution: def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]: # Initialize dummy node pointing to head dummy = ListNode(0, head) slow = dummy fast = dummy # Move fast pointer n + 1 steps forward # This ensures slow stops at the node BEFORE the one we want to delete for _ in range(n + 1): fast = fast.next # Move both pointers until fast reaches the end while fast: slow = slow.next fast = fast.next # Delete the target node slow.next = slow.next.next return dummy.next
fast pointer visits each node once, and the slow pointer visits approximately nodes.dummy, slow, and fast pointers, regardless of the input size.The Two Pointer pattern used here is fundamental for linked list problems involving position finding.
Q: Why do I get a NullPointerException or AttributeError when n equals the list length?
slow.next = slow.next.next will fail or require complex if conditions to check if the head itself is being removed.Q: Why is the wrong node being deleted (off-by-one error)?
fast pointer by exactly n steps instead of n + 1, or stopping the loop too early/late.n, when hits null, will point the node to be deleted, not its . You cannot delete a node in a singly linked list easily if you are standing on it (without copying values).nullfast is null.fastslowQ: Why does my solution fail on a single-node list?
[1] and n=1, the result should be []. If the logic assumes head is always returned, it might return [1] or crash.dummy.next, which would be null.