The problem
You get the head of a singly linked list and an integer n. Remove the n-th node counting from the end (n = 1 is the last node) and return the head of the resulting list. n is always between 1 and the list length.
Input: 1 -> 2 -> 3 -> 4 -> 5, n = 2
Output: 1 -> 2 -> 3 -> 5Counting from the end, 5 is first and 4 is second, so 4 goes.
How to think about it
Start with brute force. Walk the list once to get its length L. The node to remove is at position L − n from the start (0-indexed), so walk again to the node just before it and unlink. That is O(n) time and O(1) space, in two passes. It is a fine answer; the follow-up asks for one pass.
What you really need. To unlink a node in a singly linked list, you must stand on the node before it. So the question is: how do you stop one node before “n from the end” without knowing the length?
The key insight: a fixed gap between two pointers. Move a f pointer n nodes ahead first. Now start s and move both one step at a time. The gap stays exactly n, so when f is on the last node, s is n nodes behind it, which is exactly the node before the target. A dummy node in front of the head means “before the target” always exists, even when the target is the head itself.
Which pattern is this? Two pointers at the same speed with a head start (a cousin of fast & slow). The tell is “k-th from the end, in one pass”.
Approach
- Create a dummy node
dwhosenextishead. Putfandsond. - Move
fforwardntimes. - While
f.nextexists, movefandsone step each.fends on the last node. sis now just before the target. Skip it:s.next = s.next.next.- Return
d.next, which is the new head.
Solution
# ListNode is LeetCode's class: ListNode(val=0, next=None)
def remove_nth_from_end(head, n):
d = ListNode(0, head); f = s = d # dummy handles removing the head
for _ in range(n): f = f.next # open a gap of n nodes
while f.next: f = f.next; s = s.next # stop when f is on the last node
s.next = s.next.next # s is just before the target
return d.next
# remove_nth_from_end([1,2,3,4,5], 2) -> [1,2,3,5]Dry run
On 1 -> 2 -> 3 -> 4 -> 5 with n = 2 (D is the dummy):
| Phase | f |
s |
|---|---|---|
| start | D | D |
| after gap of 2 | 2 | D |
| move 1 | 3 | 1 |
| move 2 | 4 | 2 |
| move 3 | 5 (last) | 3 |
s is on node 3, so s.next (node 4) is skipped and the result is 1 -> 2 -> 3 -> 5.
Complexity
- Time: O(L) for a list of length L.
fwalks the list once;swalks less. - Space: O(1). One dummy node and two pointers.
Edge cases and pitfalls
- Removing the head (
nequals the length): without the dummy you would need a special case. With it,sstays on the dummy andd.nextbecomes the second node. On1 -> 2withn = 2the result is2. - Single node,
n = 1: the result is an empty list (None). - Off by one. The loop runs
while f.next, notwhile f. Withwhile f,swould land on the target itself, and you can’t unlink a node from itself in a singly linked list. - Returning
headinstead ofd.nextbreaks when the head was the node removed.
Related problems
- Middle of the Linked List (LeetCode 876): two pointers at different speeds instead of a fixed gap.
- Rotate List (LeetCode 61): find the k-th node from the end and re-link there.
- Remove Duplicates from Sorted List II (LeetCode 82): the same dummy-node trick for deleting from the front.
- 1.Middle of the Linked List (LeetCode 876)
- 2.Linked List Cycle (LeetCode 141)
- 3.Happy Number (LeetCode 202)
- 4.Palindrome Linked List (LeetCode 234)
- 5.Intersection of Two Linked Lists (LeetCode 160)
- 6.Linked List Cycle II (LeetCode 142)
- 7.Find the Duplicate Number (LeetCode 287)
- 8.Remove Nth Node From End of List (LeetCode 19)
- 9.Reorder List (LeetCode 143)
- 10.Circular Array Loop (LeetCode 457)