0019. Remove Nth Node From End of List¶
Table of Contents¶
- Problem
- Problem Breakdown
- Approach 1: Two-Pass (Count Length, Then Remove)
- Approach 2: One-Pass Two Pointers (Optimal)
- Complexity Comparison
- Edge Cases
- Common Mistakes
- Related Problems
- Visual Animation
Problem¶
| Leetcode | 19. Remove Nth Node From End of List |
| Difficulty | 🟡 Medium |
| Tags | Linked List, Two Pointers |
Description¶
Given the
headof a linked list, remove thenthnode from the end of the list and return its head.
Examples¶
Example 1:
Input: head = [1,2,3,4,5], n = 2
Output: [1,2,3,5]
Explanation: Remove the 2nd node from the end (node with value 4).
Example 2:
Input: head = [1], n = 1
Output: []
Explanation: The only node is removed, resulting in an empty list.
Example 3:
Input: head = [1,2], n = 1
Output: [1]
Explanation: Remove the last node (value 2).
Constraints¶
- The number of nodes in the list is
sz 1 <= sz <= 300 <= Node.val <= 1001 <= n <= sz
Problem Breakdown¶
1. What is being asked?¶
Given a singly-linked list and an integer n, remove the nth node from the end of the list and return the modified list's head. The challenge is that we cannot directly index from the end in a singly-linked list.
2. What is the input?¶
| Parameter | Type | Description |
|---|---|---|
head | ListNode | Head of a singly-linked list |
n | int | Position from the end of the node to remove (1-indexed) |
Important observations about the input: - n is always valid (1 <= n <= list length), so we never need to handle invalid n - The list has at least 1 node - We may need to remove the head node itself (when n equals the list length)
3. What is the output?¶
- The head of the modified linked list after removing the target node
- If the list becomes empty (single node removed), return
null/nil/None
4. Constraints analysis¶
| Constraint | Significance |
|---|---|
1 <= sz <= 30 | Small list — both O(L) approaches are fine |
1 <= n <= sz | n is always valid — no need for bounds checking |
0 <= Node.val <= 100 | Values don't affect the algorithm |
5. Step-by-step example analysis¶
Example 1: head = [1,2,3,4,5], n = 2 → [1,2,3,5]¶
Original list: 1 → 2 → 3 → 4 → 5 → null
The 2nd node from the end is node with value 4.
(Counting from end: 5 is 1st, 4 is 2nd, 3 is 3rd, ...)
Remove node 4 by linking node 3 directly to node 5:
1 → 2 → 3 → 5 → null
Result: [1, 2, 3, 5]
Example 2: head = [1], n = 1 → []¶
Original list: 1 → null
The 1st node from the end is node with value 1 (the only node).
Remove it → empty list.
Result: []
6. Key Observations¶
- Counting from the end — the nth node from end is the (length - n + 1)th node from the start.
- Dummy head trick — using a sentinel node before head avoids special-casing removal of the head node.
- Two-pointer gap — if we maintain a gap of exactly
nnodes between two pointers, when the fast pointer reaches the end, the slow pointer is right before the target. - Single-pass possible — the two-pointer technique lets us solve this in one traversal.
- Deletion in linked list — to remove a node, we need a pointer to the node before it, so we can set
prev.next = prev.next.next.
7. Pattern identification¶
| Pattern | Why it fits | Example |
|---|---|---|
| Two Pointers (Fast & Slow) | Maintain fixed gap to find nth-from-end | This problem |
| Dummy Head | Simplifies edge case when head is removed | Remove Nth Node (this problem) |
| Linked List Traversal | Fundamental operation for counting or positioning | All linked list problems |
Chosen pattern: Two Pointers with fixed gap Reason: By advancing the fast pointer n steps first, the gap ensures that when fast reaches the end, slow is positioned exactly before the node to remove — all in one pass.
Approach 1: Two-Pass (Count Length, Then Remove)¶
Thought process¶
The nth node from the end is the (L - n + 1)th node from the beginning, where L is the list length. First pass: count L. Second pass: walk to position (L - n) to find the node just before the target.
Algorithm (step-by-step)¶
- Create a dummy node pointing to
head - First pass: traverse the entire list to count its length
L - Second pass: starting from dummy, advance
L - nsteps to reach the node just before the target - Skip the target node:
curr.next = curr.next.next - Return
dummy.next
Pseudocode¶
function removeNthFromEnd(head, n):
dummy = ListNode(0)
dummy.next = head
// First pass: count length
length = 0
curr = head
while curr != null:
length++
curr = curr.next
// Second pass: find node before target
curr = dummy
for i = 0 to length - n - 1:
curr = curr.next
// Remove target
curr.next = curr.next.next
return dummy.next
Complexity¶
| Complexity | Explanation | |
|---|---|---|
| Time | O(L) | Two passes: one to count, one to find the target |
| Space | O(1) | Only a few extra pointers |
Implementation¶
Go¶
func removeNthFromEndTwoPass(head *ListNode, n int) *ListNode {
dummy := &ListNode{Next: head}
length := 0
curr := head
for curr != nil {
length++
curr = curr.Next
}
curr = dummy
for i := 0; i < length-n; i++ {
curr = curr.Next
}
curr.Next = curr.Next.Next
return dummy.Next
}
Java¶
public ListNode removeNthFromEndTwoPass(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
int length = 0;
ListNode curr = head;
while (curr != null) {
length++;
curr = curr.next;
}
curr = dummy;
for (int i = 0; i < length - n; i++) {
curr = curr.next;
}
curr.next = curr.next.next;
return dummy.next;
}
Python¶
def removeNthFromEndTwoPass(self, head, n):
dummy = ListNode(0, head)
length = 0
curr = head
while curr:
length += 1
curr = curr.next
curr = dummy
for _ in range(length - n):
curr = curr.next
curr.next = curr.next.next
return dummy.next
Dry Run¶
Input: head = [1,2,3,4,5], n = 2
First pass — count length:
1 → 2 → 3 → 4 → 5 → null
length = 5
Second pass — advance (5 - 2) = 3 steps from dummy:
dummy → [1] → [2] → [3] (curr is now at node 3)
Remove: curr.next = curr.next.next
node 3's next changes from [4] to [5]
Result: dummy.next = [1] → [2] → [3] → [5] → null = [1, 2, 3, 5] ✅
Approach 2: One-Pass Two Pointers (Optimal)¶
The problem with Two-Pass¶
Two-pass works, but requires traversing the list twice. Can we do it in a single pass?
Optimization idea¶
Two-pointer gap technique! Place two pointers starting at a dummy node. Advance the
fastpointern + 1steps ahead. Now the gap betweenfastandslowis exactlyn + 1.Move both pointers forward one step at a time. When
fastreachesnull(end of list),slowis exactly at the node before the target node.Key insight: - Gap of
n + 1ensuresslowstops one position before the nth-from-end node - Only one traversal needed
Algorithm (step-by-step)¶
- Create a dummy node pointing to
head - Set both
fastandslowto dummy - Advance
fastbyn + 1steps - Move both
fastandslowforward simultaneously untilfast == null slowis now just before the target node — remove it:slow.next = slow.next.next- Return
dummy.next
Pseudocode¶
function removeNthFromEnd(head, n):
dummy = ListNode(0)
dummy.next = head
fast = dummy
slow = dummy
// Advance fast n+1 steps
for i = 0 to n:
fast = fast.next
// Move both until fast reaches end
while fast != null:
fast = fast.next
slow = slow.next
// Remove the target node
slow.next = slow.next.next
return dummy.next
Complexity¶
| Complexity | Explanation | |
|---|---|---|
| Time | O(L) | Single pass through the list |
| Space | O(1) | Only two extra pointers |
Implementation¶
Go¶
func removeNthFromEnd(head *ListNode, n int) *ListNode {
dummy := &ListNode{Next: head}
fast := dummy
slow := dummy
for i := 0; i <= n; i++ {
fast = fast.Next
}
for fast != nil {
fast = fast.Next
slow = slow.Next
}
slow.Next = slow.Next.Next
return dummy.Next
}
Java¶
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0);
dummy.next = head;
ListNode fast = dummy;
ListNode slow = dummy;
for (int i = 0; i <= n; i++) {
fast = fast.next;
}
while (fast != null) {
fast = fast.next;
slow = slow.next;
}
slow.next = slow.next.next;
return dummy.next;
}
Python¶
def removeNthFromEnd(self, head, n):
dummy = ListNode(0, head)
fast = dummy
slow = dummy
for _ in range(n + 1):
fast = fast.next
while fast:
fast = fast.next
slow = slow.next
slow.next = slow.next.next
return dummy.next
Dry Run¶
Input: head = [1,2,3,4,5], n = 2
Setup: dummy → [1] → [2] → [3] → [4] → [5] → null
fast = dummy, slow = dummy
Step 1 — Advance fast n+1 = 3 steps:
fast: dummy → [1] → [2] → [3]
(fast is now at node 3)
Step 2 — Move both until fast is null:
Iteration 1: fast → [4], slow → [1]
Iteration 2: fast → [5], slow → [2]
Iteration 3: fast → null, slow → [3] ← STOP
Step 3 — Remove: slow.next = slow.next.next
Node [3].next changes from [4] to [5]
Result: [1] → [2] → [3] → [5] → null = [1, 2, 3, 5] ✅
Input: head = [1], n = 1
Setup: dummy → [1] → null
fast = dummy, slow = dummy
Advance fast 2 steps:
fast: dummy → [1] → null (fast is null)
While loop does not execute (fast is already null).
Remove: slow.next = slow.next.next
dummy.next changes from [1] to null
Result: null = [] ✅
Complexity Comparison¶
| # | Approach | Time | Space | Pros | Cons |
|---|---|---|---|---|---|
| 1 | Two-Pass (Count Length) | O(L) | O(1) | Simple logic, easy to understand | Requires two traversals |
| 2 | One-Pass Two Pointers | O(L) | O(1) | Single traversal, elegant | Slightly more nuanced pointer logic |
Which solution to choose?¶
- In an interview: Approach 2 — demonstrates understanding of the two-pointer technique and one-pass optimization
- In production: Both are equally valid since the time complexity is the same
- On Leetcode: Approach 2 — the follow-up explicitly asks "Could you do this in one pass?"
- For learning: Approach 1 first (build intuition), then Approach 2
Edge Cases¶
| # | Case | Input | Expected Output | Reason |
|---|---|---|---|---|
| 1 | Single node, remove it | head=[1], n=1 | [] | List becomes empty |
| 2 | Remove the head node | head=[1,2], n=2 | [2] | Head removal needs dummy node |
| 3 | Remove the tail node | head=[1,2], n=1 | [1] | Last node removal |
| 4 | Remove from middle | head=[1,2,3,4,5], n=3 | [1,2,4,5] | Middle node (value 3) removed |
| 5 | Remove 2nd from end | head=[1,2,3,4,5], n=2 | [1,2,3,5] | Classic example |
| 6 | Remove first (n = length) | head=[1,2,3,4,5], n=5 | [2,3,4,5] | Same as removing head |
| 7 | Two elements, remove last | head=[1,2], n=1 | [1] | Minimal list, tail removal |
Common Mistakes¶
Mistake 1: Not using a dummy node¶
# ❌ WRONG — fails when removing the head node
fast = head
slow = head
# ... if n == length, slow never advances, and we can't easily remove head
# ✅ CORRECT — dummy node before head handles all cases uniformly
dummy = ListNode(0)
dummy.next = head
fast = dummy
slow = dummy
Example: head=[1], n=1 — without a dummy, there's no node before the head to update its next pointer.
Mistake 2: Wrong gap size (off-by-one)¶
# ❌ WRONG — advancing fast by n steps puts slow AT the target, not before it
for _ in range(n):
fast = fast.next
# ✅ CORRECT — advance n+1 steps so slow lands one node BEFORE the target
for _ in range(n + 1):
fast = fast.next
Why: We need slow to be the predecessor of the target node to perform the deletion slow.next = slow.next.next.
Mistake 3: Forgetting to return dummy.next instead of head¶
# ❌ WRONG — if head was removed, this returns the deleted node
return head
# ✅ CORRECT — dummy.next always points to the true head
return dummy.next
Example: head=[1,2], n=2 — head (value 1) is removed. dummy.next is now [2], but head still points to the removed node [1].
Mistake 4: Not handling single-node list¶
# ❌ WRONG — assumes there's always a next node
slow.next = slow.next.next # crashes if slow.next.next is null? No — this is fine!
# Actually, slow.next.next being null is perfectly valid.
# Setting slow.next = null effectively removes the last node.
# The real danger is if slow.next is null (which can't happen with valid n).
Related Problems¶
| # | Problem | Difficulty | Similarity |
|---|---|---|---|
| 1 | 876. Middle of the Linked List | 🟢 Easy | Two-pointer technique on linked list (fast moves 2x) |
| 2 | 141. Linked List Cycle | 🟢 Easy | Fast/slow pointer pattern |
| 3 | 2. Add Two Numbers | 🟡 Medium | Linked list traversal and manipulation |
| 4 | 203. Remove Linked List Elements | 🟢 Easy | Node removal with dummy head technique |
| 5 | 237. Delete Node in a Linked List | 🟡 Medium | Different approach to node deletion |
Visual Animation¶
Interactive animation: animation.html
In the animation: - Two-Pass tab — visualizes counting the length, then finding and removing the target node - One-Pass Two Pointers tab — shows fast pointer advancing n+1 steps ahead, then both moving simultaneously until fast reaches the end - Custom input for list values and n value