2Brute2Best
🔥0

Remove Nth Node From End of List

Medium·LeetCode #19Basic Anchor
LeetCode
30-Second Mental ModelLinked List Fast & Slow — Gap / alignment (fixed gap n + 1 from a dummy)
Complexity:O(L)→O(L)
Key Insight

Put a dummy node before the head. Start slow and fast on the dummy and move fast ahead by n + 1 nodes. Now move both one step at a time. The gap never changes, so when fast becomes null, slow is n + 1 nodes before null, which is the node just before the n-th from the end. Unlink with slow->next = slow->next->next and return dummy.next.

Core Validity Invariant

The gap from slow to fast is always exactly n + 1 nodes, so when fast is null, slow is the node just before the n-th node from the end.

01

Problem

You are given the head of a singly linked list and a number n.

Delete the node that is n-th from the end of the list (the last node is 1st from the end), and return the head of the changed list.

Follow-up: can you do it in one pass over the list?

Input

The head of a linked list, and an integer n with 1 ≤ n ≤ the length of the list.

Output

The head of the list after that one node is removed. It may be a different node than before, or null if the list becomes empty.

Example 1
Input: head = [1, 2, 3, 4, 5], n = 2
Output: [1, 2, 3, 5]

Counting from the end: 5 is 1st, 4 is 2nd. So node 4 is removed.

Example 2
Input: head = [1], n = 1
Output: []

The only node is removed, so the list is empty.

Example 3
Input: head = [1, 2], n = 1
Output: [1]

The last node (2) is removed.

Constraints
  • • The list has `sz` nodes, 1 ≤ sz ≤ 30
  • • 0 ≤ Node.val ≤ 100
  • • 1 ≤ n ≤ sz
02

First, think yourself

Before opening any solution, try to answer these on paper. Use hints only when stuck — each hint is counted.

  1. 1.

    To delete a node in a singly linked list, which node do I actually need to be standing on?

  2. 2.

    What is the n-th node from the end if I count from the front instead? What do I need to know for that?

  3. 3.

    What happens if the node to delete is the head? Is there a node before it?

  4. 4.

    If two pointers are exactly k nodes apart and the front one reaches the end, where is the back one?

  5. 5.

    How far apart should they be so the back one stops one node before the target, not on it?

03

Brute force approach

05

Optimized approach

06

Why does this algorithm work?

07

Dry run

08

All approaches compared

ApproachIdeaTimeSpace
Brute ForcePass 1 counts L; pass 2 stops before index L − n and unlinks itO(L)O(1)
OptimalPut fast n + 1 ahead of slow; when fast hits null, slow is just before the targetO(L)O(1)
09

Why does this belong to this pattern?

Clues in the problem statement
  • “n-th from the end”A position measured from null. A fixed gap between two pointers measures it in one pass.
  • “remove a node”You need the node before it, so make the gap one bigger (n + 1).
  • “may remove the head”Use a dummy node so the head also has a node before it.
  • “one pass”No counting pass first. Let fast do the measuring.
singly linked list+position counted from the end+fixed gap between two pointers→Linked List Fast & Slow — Gap / alignment (fixed gap n + 1 from a dummy)

This is the anchor of the gap idea. In cycle and middle problems, fast moves faster than slow. Here both move at the same speed, and fast just starts earlier. The head start (n + 1) is chosen so that when fast reaches the end, slow is exactly where we need it. The next two questions reuse this: Intersection uses a head start equal to the length difference, and Swapping Nodes uses a gap of k − 1 to find the k-th from the end.

10

Don't make this mistake

No dummy node, so removing the head crashes
✗ ListNode* slow = head; ListNode* fast = head; for (int i = 0; i < n; i++) fast = fast->next; while (fast->next) { ... } // fast is null when n == length
✓ ListNode dummy(0, head); ListNode* slow = &dummy; ListNode* fast = &dummy; for (int i = 0; i <= n; i++) fast = fast->next;

When n equals the length, the node to remove is the head and there is no node before it. Without a dummy, fast becomes null after the head start and fast->next crashes. With a dummy, the dummy is the node before the head and the same code works.

Gap of n instead of n + 1
✗ for (int i = 0; i < n; i++) fast = fast->next; // gap n while (fast) { fast = fast->next; slow = slow->next; } slow->next = slow->next->next;
✓ for (int i = 0; i <= n; i++) fast = fast->next; // gap n + 1

With a gap of n, slow lands on the n-th node from the end, not before it. Then slow->next = slow->next->next deletes the node after the target. On [1, 2, 3, 4, 5], n = 2 you'd remove 5 instead of 4.

Returning head instead of dummy.next
✗ slow->next = slow->next->next; return head;
✓ return dummy.next;

If the head was removed, head still points at the removed node. dummy.next always points at the real first node of the new list.

Thinking fast must move twice as fast

This is the pattern-confusion trap: in cycle and middle questions fast moves 2 steps per turn. Here the speed is the same (1 and 1); only the start differs. If fast moved 2 steps, the gap would keep growing and slow would stop near the middle, not near the end.

11

Want to implement this?

Write it yourself on LeetCode, then come back and compare your code with the solutions above.

Open coding workspace
How did it go?
Beyond The LeetCode Problem

How this concept connects to System Design & C++ Systems

Bridge DSA with production engineering

Top engineering interviews test whether you understand how algorithms like Linked List — Fast & Slow translate into production architectures and low-level memory mechanics.