Find All Duplicates in an Array looks like a job for a hash set — but it hides a beautiful constraint that lets you solve it in O(1) extra space. The trick is index marking: use the array itself as your memory.
Problem. Given an integer array nums of length n where every value is in the range 1..n,
some elements appear once and some appear twice. Return all the elements that appear twice —
aiming for O(n) time while using only the input array itself as scratch space (no separate hash set).
Example: nums = [1, 3, 3, 2, 1] → answer [3, 1] (3 and 1 each appear twice).
The slow way first
The obvious idea: keep a hash set (or a count map). For each number, check if it is already in the set; if so, it is a duplicate, otherwise add it. That is O(n) time — but it uses O(n) extra space, and the problem explicitly asks for none.
The question to ask: what do I already have that could store the bookkeeping for free? The array itself. The values are all in 1..n, so each value can point at a unique slot — and a slot can carry one extra bit of information by its sign.
The idea: let each value mark its own slot
Every value v is in 1..n, so v "owns" the slot at index v - 1. We walk the array and, for each value x, look at the owned slot i = abs(x) - 1:
- If
nums[i]is positive, this is the first time we have seen the valueabs(x). Flip the sign ofnums[i]to negative to mark it visited. - If
nums[i]is already negative, we have visited that slot before — soabs(x)is a duplicate. Collect it.
We use abs(x) everywhere because earlier steps may have flipped the very cell we are reading, and we must recover the original value.
The sign is a free extra bit of storage: positive means "not yet seen," negative means "seen once." No hash map required.
Walk through it
Step through the animation. The pointer x scans left to right; the pointer i = abs(x)-1 jumps to the slot each value owns. The first time a slot is touched it turns negative (visited). When x lands on a slot that is already negative, we have found a duplicate and add abs(x) to res. For [1, 3, 3, 2, 1] we catch 3 on the third value and 1 on the last, returning [3, 1].
Pseudocode
res = empty list
for each value x in nums:
i = abs(x) - 1 # the slot this value owns
if nums[i] < 0: # slot already marked?
add (i + 1) to res # abs(x) is a duplicate
else:
nums[i] = -nums[i] # mark the slot visited
return resThe Python solution
def find_duplicates(nums):
res = []
for x in nums:
i = abs(x) - 1
if nums[i] < 0:
res.append(i + 1)
else:
nums[i] *= -1
return resrescollects the answers; no other data structure is allocated.i = abs(x) - 1is the slot the current value owns. We takeabsbecause an earlier step may have already flippedxto negative.if nums[i] < 0is the visited check — the slot's sign is our one bit of memory.res.append(i + 1)records the duplicate. The owned value isi + 1, which equalsabs(x).nums[i] *= -1marks the slot visited the first time we reach it.
Complexity
| Case | Time | Notes |
|---|---|---|
| Hash-set approach | O(n) (moderate) | fast, but O(n) extra space |
| Index marking (this solution) | O(n) (moderate) | one pass, signs as memory |
O(1) (fast)We match the hash set on time, O(n), but use only O(1) extra space (ignoring the output list). The whole trick is reusing the input as scratch storage — a move that only works because the values are bounded by 1..n.
When this pattern shows up
Whenever values are constrained to 1..n (or 0..n-1) and the problem wants O(1) space, think
index marking: each value can flag the slot it owns by sign-flipping, by adding n, or by swapping it
into place. "Find all duplicates," "find the missing number," "first missing positive," and "find all
disappeared numbers" are all the same idea.
Always read the value through abs(...). Once a slot has been flipped negative, reading it raw gives
you the wrong index. Forgetting the abs is the single most common bug in this pattern.
Practice
For nums = [1, 3, 3, 2, 1], when x is the second 3 (the value at index 2), which slot does it check and what does it find?
1. Why can we use the array itself as storage instead of a hash set?
2. What does a negative value at nums[i] mean?
3. Why do we compute i using abs(x) - 1 rather than x - 1?
4. What is the extra space used by this solution, ignoring the output list?