19. Remove Nth Node From End of List
Quick Identifier
- Topic: linked-list
- Pattern: Fast & Slow Pointers (gap / offset)
- Difficulty: Medium
- Companies: Amazon, Google, Meta, Apple, Adobe
- Frequency: Very High
- LeetCode: 19
Quick Sights
- Time Complexity:
O(n)from the optimal approach. - Space Complexity:
O(1)from the optimal approach. - Core Mechanism: Given a singly linked list and
nthFromEnd, remove the node that isnthFromEndsteps from the tail and return the head (first node may be removed).
Concept Explanation
Given a singly linked list and nthFromEnd, remove the node that is nthFromEnd steps from the tail and return the head (first node may be removed).
The key is to state the invariant before coding: what part of the input is already settled, what state is still unresolved, and what single operation makes progress without losing correctness.
Recognition cues:
- “Nth from end” with one pass requirement
- Fast pointer leads slow by
nthFromEndgaps - Dummy head simplifies removing the first node
Study Pattern (SDE3+)
- 0-3 min: restate I/O and brittle edges aloud, then verbalize two approaches with time/space before you touch the keyboard.
- Implementation pass: one-line invariant above the tight loop; state what progress you make each iteration.
- 5 min extension: pick one constraint twist (immutable input, stream, bounded memory, parallel readers) and explain what in your solution breaks without a refactor.
Diagram
This diagram shows the algorithm movement for this problem family.
Loading diagram…
Approaches
- Brute force — two passes: measure length
length, remove at indexlength - nthFromEnd. - Better — one pass with array of pointers —
O(n)space. - Optimal —
O(n)time /O(1)space — advancefastbynthFromEnd+1from dummy, then move both untilfasthits end;slow.Nextis the node to skip.
Optimal Solution
Go
type ListNode struct {
Val int
Next *ListNode
}
func removeNthFromEnd(head *ListNode, nthFromEnd int) *ListNode {
dummy := &ListNode{Next: head}
lead := dummy
for step := 0; step <= nthFromEnd; step++ {
lead = lead.Next
}
trail := dummy
for lead != nil {
lead = lead.Next
trail = trail.Next
}
trail.Next = trail.Next.Next
return dummy.Next
}JavaScript
class ListNode {
constructor(val = 0, next = null) {
this.val = val;
this.next = next;
}
}
function removeNthFromEnd(head, nthFromEnd) {
const dummy = new ListNode(0, head);
let lead = dummy;
for (let step = 0; step <= nthFromEnd; step++) {
lead = lead.next;
}
let trail = dummy;
while (lead !== null) {
lead = lead.next;
trail = trail.next;
}
trail.next = trail.next.next;
return dummy.next;
}Walkthrough
head = [1,2,3,4,5], nthFromEnd = 2 → remove value 4.
| step | lead position after advancing | trail stops before |
|---|---|---|
| init | dummy + n+1 nodes ahead | node before target |
When lead is nil, trail.Next is the 4 node; unlink it.
Edge Cases
- Remove first node — dummy absorbs head change.
- Single node list with
nthFromEnd = 1. nthFromEndequals list length — remove head.
Pitfalls
- Off-by-one in how many steps
leadstarts ahead (needsn+1from dummy to stop before target). - Not returning
dummy.Nextafter mutating.
Similar Problems
- 141. Linked List Cycle — fast/slow traversal discipline.
- 143. Reorder List — slow/fast finds middle before further ops.
- 206. Reverse Linked List — dummy-node patterns pair well with boundary edits.
Variants
- Remove all nodes matching value from end rule — different unlink criteria.
- Doubly linked list — find node with one pass from tail pointer.
Mind-Map Tags
#linked-list #two-pointer #dummy-head #nth-from-end
Mark this page when you finish learning it.
Last updated on
Spotted something unclear or wrong on this page?