#19 - Remove Nth Node From End of List
June 23, 2025
Remove Nth Node From End of List
- Difficulty: Medium
- Topics: Linked List, Two Pointers
- Link: https://leetcode.com/problems/remove-nth-node-from-end-of-list/
Problem Description
Given the head
of a linked list, remove the nth
node from the end of the list and return its head.
Example 1:
Input: head = [1,2,3,4,5], n = 2
Output: [1,2,3,5]
Example 2:
Input: head = [1], n = 1
Output: []
Example 3:
Input: head = [1,2], n = 1
Output: [1]
Constraints:
- The number of nodes in the list is
sz
. 1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
Follow up: Could you do this in one pass?
Solution
1. Problem Deconstruction
Technical Restatement
Given the head node of a singly linked list and an integer n
, remove the node positioned n
places from the end of the list. Return the modified list’s head. Constraints:
- List size
sz
satisfies1 ≤ sz ≤ 30
. - Node values:
0 ≤ val ≤ 100
. n
satisfies1 ≤ n ≤ sz
.
The core challenge is achieving this with a single traversal (follow-up).
Beginner Explanation
Imagine a conga line of people holding shoulders. You know the first person (head). Your task: remove the person who is n
positions from the end. After removal, reconnect the line. For example:
- Line: Alice → Bob → Charlie → Dave → Eve (
n=2
). Remove Dave (2nd from end). New line: Alice → Bob → Charlie → Eve. - If the line has 1 person (
n=1
), return an empty line.
Mathematical Formulation
Let the list be a sequence of nodes: where . Remove node at position (1-indexed). The new list is:
Return (or None if and ).
Constraint Analysis
1 ≤ sz ≤ 30
:- Limits solution space; even is acceptable.
- Edge: Single-node list removal → return
None
.
0 ≤ Node.val ≤ 100
:- Values are non-negative and bounded; no impact on algorithm design.
1 ≤ n ≤ sz
:- Guarantees
n
is valid. Critical edge:n = sz
(remove head) orn = 1
(remove tail).
- Guarantees
2. Intuition Scaffolding
Analogies
- Real-World (Queue Management):
A store queue (30 people max). Manager wants to remove then
-th person from the end. Use two assistants:- Assistant A starts at the front, walks
n+1
positions ahead. - Assistant B starts at the front. Both walk at same speed. When A reaches the end, B is behind the target. Remove B’s next person.
- Assistant A starts at the front, walks
- Gaming (Snake Segment Removal):
In a snake game, remove then
-th segment from the tail. Place two markers:- Marker 1:
n+1
segments ahead of Marker 2. - Move both toward the tail. When Marker 1 hits the tail, Marker 2 is before the target.
- Marker 1:
- Mathematical (Fixed Offset):
Let = distance between two pointers. Set . When the leading pointer traverses nodes (reachingNone
), the trailing pointer is at position . The target is at (1-indexed).
Common Pitfalls
- Head Removal Edge Case:
Ifn = sz
, the head must be removed. Solution: Use a dummy head to unify logic. - Off-by-One in Pointer Gap:
Setting gap ton
(notn+1
) lands the trailing pointer on the target, making removal impossible. - Single-Node List:
Forgetting to returnNone
after removal. - Null Pointer in Traversal:
Accessingnext
ofNone
whenn
is larger than list size (handled by constraints). - Two-Pass Inefficiency:
Counting nodes first (pass 1) then removing (pass 2) fails the one-pass follow-up.
3. Approach Encyclopedia
Approach 1: Two-Pass (Brute Force)
Pseudocode:
def removeNthFromEnd(head, n):
sz = 0
curr = head
while curr: # Pass 1: Count nodes
sz += 1
curr = curr.next
target_idx = sz - n # 0-indexed position to remove
if target_idx == 0: # Remove head
return head.next
curr = head
for _ in range(target_idx - 1): # Traverse to node before target
curr = curr.next
curr.next = curr.next.next # Remove target
return head
Complexity Proof:
- Time: . Pass 1: nodes. Pass 2: Up to nodes.
- Space: .
Visualization:
Example: [1,2,3,4,5], n=2
Pass 1: Count = 5 → target_idx = 3 (0-indexed value=4).
Pass 2: Traverse to index 2 (node 3):
1 → 2 → 3 → 4 → 5
| |
+--------+ → 1 → 2 → 3 → 5
Approach 2: One-Pass (Two Pointers)
Pseudocode:
def removeNthFromEnd(head, n):
dummy = ListNode(0, head) # Dummy head
first = second = dummy
for _ in range(n + 1): # Create n+1 gap
first = first.next
while first: # Traverse until first is None
first = first.next
second = second.next
second.next = second.next.next # Remove target
return dummy.next
Complexity Proof:
- Time: .
first
traverses nodes. - Space: .
Visualization:
Example: [1,2,3,4,5], n=2, k=5
Step 1: Dummy(0) → 1 → 2 → 3 → 4 → 5
second first (after n+1=3 steps: first at 3)
Step 2: Move both until first=None:
0 → 1 → 2 → 3 → 4 → 5 → None
second first
Step 3: Remove second.next (4):
0 → 1 → 2 → 3 → 5
4. Code Deep Dive
Optimal Solution (One-Pass)
class Solution:
def removeNthFromEnd(self, head: Optional[ListNode], n: int) -> Optional[ListNode]:
dummy = ListNode(0, head) # Dummy node to simplify head removal
first = second = dummy # Initialize pointers
for _ in range(n + 1): # Create gap of n+1 nodes
first = first.next
while first: # Traverse until first hits end
first = first.next
second = second.next
second.next = second.next.next # Remove target node
return dummy.next # Return new head (skips dummy)
Line-by-Line Annotations
dummy = ListNode(0, head)
:- What: Sentinel node pointing to
head
. - Why: Unifies head removal (when
n = sz
) with other cases.
- What: Sentinel node pointing to
first = second = dummy
:- What: Initialize pointers at dummy.
- Why: Synchronized start for gap creation.
for _ in range(n + 1)
:- What: Advance
first
byn+1
nodes. - Why: Creates fixed gap of
n+1
sosecond
lands before the target.
- What: Advance
while first
:- What: Move both pointers until
first
isNone
. - Why: When
first
reaches end,second
is before the target.
- What: Move both pointers until
second.next = second.next.next
:- What: Skip the target node.
- Why: Removes the node at position
k - n + 1
.
return dummy.next
:- What: Return head of modified list.
- Why:
dummy.next
points to new head (handles removal of original head).
Edge Case Handling
- Example 2 (Single Node):
head = [1]
,n=1
.dummy(0) → 1 → None
.first
moves2
steps: to1
thenNone
.while first
skipped →second
atdummy
.dummy.next = None
→ returnNone
.
- Head Removal (
n = sz
):
head = [1,2,3,4,5]
,n=5
.- After gap creation,
first
atNone
. second
atdummy
→ removedummy.next
(original head).- New head:
dummy.next.next = 2
.
- After gap creation,
5. Complexity War Room
Hardware-Aware Analysis
- At
sz=30
, memory:- Nodes: 30 × (4B val + 8B pointer) = 360B.
- Pointers: 3 × 8B = 24B.
Total: < 400B → Fits in L1 CPU cache.
Approach Comparison
Approach | Time | Space | Readability | Interview Viability |
---|---|---|---|---|
Two-Pass | O(k) | O(1) | ★★★★★★★★☆☆ (8/10) | ✅ (but not one-pass) |
One-Pass | O(k) | O(1) | ★★★★★★★☆☆☆ (7/10) | ✅ (optimal) |
6. Pro Mode Extras
Variants
- Remove from Beginning:
Traverse to(n-1)
-th node and remove next. - Two Transactions (LC 123):
Extension: Track max profit with two buys/sells. Requires DP with states. - Circular List:
Use fast-slow pointers to detect cycle, then adjust removal logic.
Interview Cheat Sheet
- Key Insight: Two pointers with fixed gap handle “n-th from end”.
- Must-Mention: Dummy node for edge cases, one-pass efficiency.
- Time/Space: Always state first.
- Verify: Test
n=1
,n=sz
, and single-node cases.