Editorial
Core insight
Remove Nth Node From End of List · Two Pointers
Core Insight for Remove Nth Node From End of List
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.

Deep dive
Full breakdown
Full reasoning, pitfalls, and implementation flow
LeetCode 19: Remove Nth Node From End of List Solution & Explanation
Problem Overview
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.
Brute Force Approach for Remove Nth Node From End of List
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.
- First Pass: Traverse the entire linked list starting from the head to count the total number of nodes, denoted as .
- Calculate Target: The node to remove is located at the index (0-indexed). The node before the target (the predecessor) is at index .
- Second Pass: Traverse the list again from the head, stopping at the -th node.
- Removal: Update the
nextpointer of the predecessor to skip the target node.
Pseudo-code:
function 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 headWhy 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.
Algorithm Strategy: Two Pointer Patterns
- Dummy Node Initialization: Create a dummy node (sentinel) pointing to the
headof the list. This simplifies edge cases, such as removing the head node itself (where the -th node from the end is the first node). - Pointer Setup: Initialize two pointers,
slowandfast, both pointing to the dummy node. - Create the Gap: Advance the
fastpointer forward by steps. This establishes the necessary separation so thatslowwill point to the predecessor of the target node later. - Slide the Window: Move both
slowandfastpointers forward one node at a time untilfastbecomesnull. - Perform Deletion: At this state,
slowpoints to the node immediately before the target node. Updateslow.nextto point toslow.next.next, effectively removing the target node from the list. - Return Result: Return
dummy.nextas the new head of the list.
Execution Flow
Let's trace the algorithm with Input: head = [1, 2, 3, 4, 5], n = 2.
-
Setup:
- Create
dummynode withval=0,next=head. - List:
dummy -> 1 -> 2 -> 3 -> 4 -> 5 -> null. slowpoints todummy.fastpoints todummy.
- Create
-
Create Gap:
- Move
fast(which is 3) steps. - Step 1:
fastat1. - Step 2:
fastat2. - Step 3:
fastat3. - Current state:
slowatdummy,fastat3. Gap is established.
- Move
-
Slide Window:
- Loop condition:
fastis notnull. - Iteration 1:
slowmoves to1.fastmoves to4.
- Iteration 2:
slowmoves to2.fastmoves to5.
- Iteration 3:
slowmoves to3.fastmoves tonull.
- Loop terminates because
fastisnull.
- Loop condition:
-
Deletion:
slowis at node3. The target to remove isslow.next(node4).- Operation:
slow.next = slow.next.next(points3to5). - List becomes:
dummy -> 1 -> 2 -> 3 -> 5 -> null.
-
Return:
- Return
dummy.next(node1).
- Return
Proof of Correctness
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 .
- We advance
fastby steps.fastis now at index (relative to the original list, or steps from dummy). - The distance from
fastto the end (null, effectively index ) is steps? No, simpler:faststarts at dummy. After steps,fastis at the -th node of the list. - Let be the number of steps
fasttakes to reachnullfrom its current position. slowalso takes steps from the dummy node.- When
fastreachesnull, it has traversed a total of nodes (including dummy). slowis steps behindfast.- Therefore,
slow's position is steps from the start (including dummy). - In terms of list indices (0-indexed),
slowlands on index , which is exactly the predecessor of the target node at .
Pattern Reuse Notes
The Two Pointer pattern used here is fundamental for linked list problems involving position finding.
- LeetCode 876: Middle of the Linked List: Uses two pointers moving at different speeds (1x and 2x) to find the middle. While the "separation" isn't fixed, the concept of using relative pointer positions to find a target without calculating length is identical.
- LeetCode 2095: Delete the Middle Node of a Linked List: Directly applies the logic from LC 876 to find the predecessor of the middle node, similar to how we find the predecessor of the -th node here.
Feynman coach · Before you peek at the code
Can you explain the core insight in your own words?
If you can explain it before reading code, you’re far more likely to recall it under interview pressure.
Reference Implementation
C++ Solution for LeetCode 19
1/**
2 * Definition for singly-linked list.
3 * struct ListNode {
4 * int val;
5 * ListNode *next;
6 * ListNode() : val(0), next(nullptr) {}
7 * ListNode(int x) : val(x), next(nullptr) {}
8 * ListNode(int x, ListNode *next) : val(x), next(next) {}
9 * };
10 */
11class Solution {
12public:
13 ListNode* removeNthFromEnd(ListNode* head, int n) {
14 // Use a dummy node to handle edge cases like removing the head
15 ListNode* dummy = new ListNode(0);
16 dummy->next = head;
17
18 ListNode* slow = dummy;
19 ListNode* fast = dummy;
20
21 // Advance fast pointer so that the gap between fast and slow is n nodes
22 // We move n + 1 times to land slow on the predecessor of the target
23 for (int i = 0; i <= n; ++i) {
24 fast = fast->next;
25 }
26
27 // Move both pointers until fast reaches the end
28 while (fast != nullptr) {
29 slow = slow->next;
30 fast = fast->next;
31 }
32
33 // Remove the nth node from the end
34 ListNode* nodeToDelete = slow->next;
35 slow->next = slow->next->next;
36 delete nodeToDelete; // Good practice in C++ to free memory
37
38 ListNode* newHead = dummy->next;
39 delete dummy; // Clean up dummy node
40 return newHead;
41 }
42};Java Solution for LeetCode 19
1/**
2 * Definition for singly-linked list.
3 * public class ListNode {
4 * int val;
5 * ListNode next;
6 * ListNode() {}
7 * ListNode(int val) { this.val = val; }
8 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
9 * }
10 */
11class Solution {
12 public ListNode removeNthFromEnd(ListNode head, int n) {
13 // Dummy node simplifies edge cases (e.g., removing the only node)
14 ListNode dummy = new ListNode(0);
15 dummy.next = head;
16
17 ListNode slow = dummy;
18 ListNode fast = dummy;
19
20 // Move fast pointer n + 1 steps ahead
21 for (int i = 0; i <= n; i++) {
22 fast = fast.next;
23 }
24
25 // Move both pointers until fast reaches the end
26 while (fast != null) {
27 slow = slow.next;
28 fast = fast.next;
29 }
30
31 // Skip the desired node
32 slow.next = slow.next.next;
33
34 return dummy.next;
35 }
36}Python Solution for LeetCode 19
1# Definition for singly-linked list.
2# class ListNode:
3# def __init__(self, val=0, next=None):
4# self.val = val
5# self.next = next
6class Solution:
7 def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
8 # Initialize dummy node pointing to head
9 dummy = ListNode(0, head)
10 slow = dummy
11 fast = dummy
12
13 # Move fast pointer n + 1 steps forward
14 # This ensures slow stops at the node BEFORE the one we want to delete
15 for _ in range(n + 1):
16 fast = fast.next
17
18 # Move both pointers until fast reaches the end
19 while fast:
20 slow = slow.next
21 fast = fast.next
22
23 # Delete the target node
24 slow.next = slow.next.next
25
26 return dummy.next