LeetCode #442 Medium

Find All Duplicates in an Array

Find All Duplicates in an Array: every integer in nums lies in [1, n] and appears once or twice. Return all the values appearing twice, in O(n) time and without extra space.

Constraints
  • n == nums.length
  • 1 <= n <= 10⁵
  • 1 <= nums[i] <= n
  • Each element in nums appears once or twice
arrayhash table
Open on LeetCode ↗
Find All Duplicates in an Array diagramA labelled diagram of the structure this problem turns on.value v owns index v − 1 — the array is its own hash table4327idx 0idx 1idx 2idx 3value 3 → index 2negate that slot to mark the value seen; find it already negativeand the value is a duplicate. abs() recovers the magnitude, whichearlier negations never touched — so O(1) extra space
02

Intuition

The constraint that every value lies in [1, n] means each value names a valid index — the array can act as its own hash table. Visiting value v and flipping the sign at index v - 1 marks that value as seen; encountering an already-negative slot proves v has been visited before. The sign bit supplies one bit of storage per element, which is exactly what a seen set needs, so no extra space is required.

How to spot this pattern

When values are bounded by the array length and O(1) space is demanded, the array itself is the intended storage — via sign marking or cyclic swapping. The tell is the value range matching the index range. Missing Number, First Missing Positive, and Find the Duplicate Number all exploit it.

03

Approach

Try it first

Before reading on: work out why the constraint 1 <= nums[i] <= n is what makes the array usable as its own hash table. Then decide why abs() is needed when computing the index.

1

The value range is the whole trick

Values in [1, n] map one-to-one onto indices [0, n-1] via v - 1. That is what turns the input array into usable scratch space: the slot for value v is a fixed, collision-free location. Without that guarantee — if values could exceed n or be negative — the index would be out of range and the technique would not apply at all. Recognising the range constraint as the enabling condition, rather than incidental detail, is the actual insight.

2

Encoding *seen* in the sign bit

Iterate the array, and for each element take index = abs(num) - 1. The abs is essential because earlier iterations may already have negated this element, and the magnitude still carries the original value. If nums[index] is already negative, then some earlier element also mapped here, so index + 1 appears twice and is recorded. Otherwise negate nums[index] to mark it seen. Each value's presence is stored destructively but reversibly — the magnitudes are untouched, so the original array can be restored by taking absolute values.

3

Why this beats the obvious alternatives

A hash set solves it in O(n) time but uses O(n) space, violating the follow-up. Sorting gives O(1) extra space but costs O(n log n) and destroys the ordering. The sign-marking approach achieves both bounds at once because it reuses storage the input already occupies. Each value appears at most twice, so each slot is negated at most once and the duplicate is detected on the second visit — one pass, O(n) time, O(1) extra space beyond the output list.

04

Solution & live demo

1class Solution:
2 def findDuplicates(self, nums):
3 result = []
4 for num in nums:
5 index = abs(num) - 1
6 if nums[index] < 0:
7 result.append(index + 1)
8 else:
9 nums[index] = -nums[index]
10 return result
05

Common pitfalls

Indexing without abs

✗ Wrong
index = num - 1
✓ Right
index = abs(num) - 1

A previous iteration may already have negated this element, making num negative and the index negative or out of range. The magnitude still holds the original value, so abs recovers it.

Using a hash set

✗ Wrong
seen = set()
for num in nums:
    if num in seen:
        result.append(num)
    seen.add(num)
✓ Right
mark the sign at index abs(num) - 1

Correct and O(n) time, but it uses O(n) extra space, which the problem's follow-up explicitly rules out. The sign bit provides the same one-bit-per-value information for free.

Appending nums[index] instead of index + 1

✗ Wrong
result.append(nums[index])
✓ Right
result.append(index + 1)

By this point nums[index] has been negated, so it holds the negative of some value rather than the duplicate itself. The duplicated value is what the index encodes: index + 1.

06

Edge cases

No duplicates at all

Every slot is negated exactly once and an empty list is returned.

Every value duplicated

Each pair triggers one detection, so n/2 values are returned.

Duplicate at the array's own index, e.g. [1,1]

Index 0 is negated then found negative, reporting 1.

Single element [1]

One negation, no duplicate, empty result.

Elements already negated by earlier steps

abs(num) recovers the original value, so the mapping stays correct.

07

Complexity

Time
O(n)
Space
O(1)
One pass, mutating the input in place. The output list is not counted as extra space.