85. Find All Duplicates in an Array
๐ LeetCode Problem: 442. Find All Duplicates in an Array
๐ Difficulty: Medium
๐ท๏ธ Topics: Array, Hash Table
Problem Statement
Given an integer array nums of length n where all the integers are in the range [1, n] and each integer appears once or twice, return an array of all the integers that appears twice.
You must write an algorithm that runs in O(n) time and uses only constant extra space.
Example 1:
Input: nums = [4,3,2,7,8,2,3,1]
Output: [2,3]
Explanation:
2 appears twice
3 appears twice
Example 2:
Input: nums = [1,1,2]
Output: [1]
Explanation:
1 appears twice
Example 3:
Input: nums = [1]
Output: []
Explanation:
No duplicates
Constraints:
- n == nums.length
- 1 <= n <= 10^5
- 1 <= nums[i] <= n
- Each element in nums appears once or twice.
๐ ELI5: The Parking Spots Idea!
Imagine numbered parking spots:
You have 8 parking spots numbered 1-8
You have 8 cars with numbers on them
Perfect scenario:
Each car parks at its own number
Car 1 โ Spot 1
Car 2 โ Spot 2
...
But wait! You have: [4, 3, 2, 7, 8, 2, 3, 1]
Two "Car 2"s โ Both want Spot 2!
Two "Car 3"s โ Both want Spot 3!
When we try to park all cars:
Car 4 โ Spot 4 โ
Car 3 โ Spot 3 โ
Car 2 โ Spot 2 โ
Car 7 โ Spot 7 โ
Car 8 โ Spot 8 โ
Car 2 โ Spot 2 (already occupied!) โ Duplicate!
Car 3 โ Spot 3 (already occupied!) โ Duplicate!
Car 1 โ Spot 1 โ
Duplicates: 2 and 3! โ
The Key Insight:
Use Cyclic Sort to place each car at its spot!
Number i should be at index i-1
When we try to swap:
If target spot already has the correct number
โ We found a duplicate!
Example:
Trying to place second "2" at index 1
But index 1 already has 2!
โ 2 is a duplicate! โ
๐จ Visual Understanding
The Problem Visualized
Example: nums = [4, 3, 2, 7, 8, 2, 3, 1]
Perfect placement (what we want):
Index: 0 1 2 3 4 5 6 7
Value: 1 2 3 4 5 6 7 8
Actual array (what we have):
Index: 0 1 2 3 4 5 6 7
Value: 4 3 2 7 8 2 3 1
Numbers present:
1 (once), 2 (twice), 3 (twice), 4 (once),
7 (once), 8 (once)
Missing: 5, 6
Duplicates: 2, 3 โ
The Cyclic Sort Process
Goal: Try to place each number at its correct index
Number i โ Index i-1
When we find duplicate:
Target index already has the correct number!
Trace: [4, 3, 2, 7, 8, 2, 3, 1]
Step 1: i=0, nums[0]=4
4 should go to index 3
Swap with index 3
[7, 3, 2, 4, 8, 2, 3, 1]
Step 2: i=0, nums[0]=7
7 should go to index 6
Swap with index 6
[3, 3, 2, 4, 8, 2, 7, 1]
Step 3: i=0, nums[0]=3
3 should go to index 2
Swap with index 2
[2, 3, 3, 4, 8, 2, 7, 1]
Step 4: i=0, nums[0]=2
2 should go to index 1
Swap with index 1
[3, 2, 3, 4, 8, 2, 7, 1]
Step 5: i=0, nums[0]=3
3 should go to index 2
But nums[2] is already 3!
โ Duplicate found! (but don't add yet)
Move to next: i++
Step 6: i=1, nums[1]=2
2 should be at index 1 โ
Already correct! i++
Step 7: i=2, nums[2]=3
3 should be at index 2 โ
Already correct! i++
... Continue for rest ...
Final: [3, 2, 3, 4, 8, 2, 7, 1]
Now find duplicates:
Check each index i:
If nums[i] != i+1 โ duplicate exists!
Index 0: nums[0]=3, expected 1 (not equal)
Index 5: nums[5]=2, expected 6 (not equal)
Wait, we need different approach for finding...
๐ฏ Approach 1: Hash Set (Simple)
The Most Natural Thinking ๐ญ
Core Logic:
Use a set to track seen numbers
If we see a number again โ it's a duplicate
Implementation
/**
* Using HashSet
* Time: O(n), Space: O(n)
*/
public List<Integer> findDuplicates(int[] nums) {
List<Integer> duplicates = new ArrayList<>();
Set<Integer> seen = new HashSet<>();
for (int num : nums) {
if (seen.contains(num)) {
duplicates.add(num); // Found duplicate!
} else {
seen.add(num);
}
}
return duplicates;
}
โฐ Time: O(n) - Single pass
๐พ Space: O(n) - HashSet
โ Problem: Uses O(n) space! Can we do O(1)?
๐ฏ Approach 2: Cyclic Sort (O(1) Space) โญ
Better Approach ๐ก
๐ง The Core Insight
Use Cyclic Sort to place numbers at correct positions!
After sorting:
Each index i should have value i+1
If nums[i] != i+1 โ that index has a duplicate
Key observation:
If 2 appears twice:
One "2" at index 1 (correct)
Other "2" somewhere else (wrong position)
The index with wrong value tells us which number is duplicate!
Implementation
/**
* Cyclic Sort - Find Duplicates
* Time: O(n), Space: O(1)
*/
public List<Integer> findDuplicates(int[] nums) {
int i = 0;
// STEP 1: Cyclic sort
while (i < nums.length) {
int correctIndex = nums[i] - 1;
// If number is not at correct position
if (nums[i] != nums[correctIndex]) {
swap(nums, i, correctIndex);
} else {
i++;
}
}
// STEP 2: Find duplicates
List<Integer> duplicates = new ArrayList<>();
for (i = 0; i < nums.length; i++) {
// If index i doesn't have i+1, then i+1 is duplicate
if (nums[i] != i + 1) {
duplicates.add(nums[i]);
}
}
return duplicates;
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
โฐ Time: O(n) - Each number swaps at most once
๐พ Space: O(1) - Excluding result list
๐ Super Detailed Dry Run
Example: nums = [4, 3, 2, 7, 8, 2, 3, 1]
Goal: Find duplicates [2, 3]
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
STEP 1: Cyclic Sort
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Initial: [4, 3, 2, 7, 8, 2, 3, 1]
Index: 0 1 2 3 4 5 6 7
i = 0:
nums[0] = 4
correctIndex = 4 - 1 = 3
nums[0] != nums[3]? 4 != 7? Yes, swap!
Swap nums[0] with nums[3]
Array: [7, 3, 2, 4, 8, 2, 3, 1]
i stays at 0
i = 0:
nums[0] = 7
correctIndex = 7 - 1 = 6
nums[0] != nums[6]? 7 != 3? Yes, swap!
Array: [3, 3, 2, 4, 8, 2, 7, 1]
i stays at 0
i = 0:
nums[0] = 3
correctIndex = 3 - 1 = 2
nums[0] != nums[2]? 3 != 2? Yes, swap!
Array: [2, 3, 3, 4, 8, 2, 7, 1]
i stays at 0
i = 0:
nums[0] = 2
correctIndex = 2 - 1 = 1
nums[0] != nums[1]? 2 != 3? Yes, swap!
Array: [3, 2, 3, 4, 8, 2, 7, 1]
i stays at 0
i = 0:
nums[0] = 3
correctIndex = 3 - 1 = 2
nums[0] != nums[2]? 3 != 3? No!
Already there (duplicate!)
i++
Array: [3, 2, 3, 4, 8, 2, 7, 1]
i = 1
i = 1:
nums[1] = 2
correctIndex = 2 - 1 = 1
nums[1] != nums[1]? 2 != 2? No!
Already at correct position! i++
Array: [3, 2, 3, 4, 8, 2, 7, 1]
i = 2
i = 2:
nums[2] = 3
correctIndex = 3 - 1 = 2
Already at correct position! i++
Array: [3, 2, 3, 4, 8, 2, 7, 1]
i = 3
i = 3:
nums[3] = 4
Already at correct position! i++
i = 4:
nums[4] = 8
correctIndex = 8 - 1 = 7
nums[4] != nums[7]? 8 != 1? Yes, swap!
Array: [3, 2, 3, 4, 1, 2, 7, 8]
i stays at 4
i = 4:
nums[4] = 1
correctIndex = 1 - 1 = 0
nums[4] != nums[0]? 1 != 3? Yes, swap!
Array: [1, 2, 3, 4, 3, 2, 7, 8]
i stays at 4
i = 4:
nums[4] = 3
correctIndex = 3 - 1 = 2
nums[4] != nums[2]? 3 != 3? No!
Duplicate! i++
Array: [1, 2, 3, 4, 3, 2, 7, 8]
i = 5
i = 5:
nums[5] = 2
correctIndex = 2 - 1 = 1
nums[5] != nums[1]? 2 != 2? No!
Duplicate! i++
i = 6:
nums[6] = 7
Already at correct position! i++
i = 7:
nums[7] = 8
Already at correct position! i++
i = 8, exit while loop
Final sorted: [1, 2, 3, 4, 3, 2, 7, 8]
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
STEP 2: Find Duplicates
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Check each index:
i = 0: nums[0] = 1, expected 1 (0+1) โ
i = 1: nums[1] = 2, expected 2 (1+1) โ
i = 2: nums[2] = 3, expected 3 (2+1) โ
i = 3: nums[3] = 4, expected 4 (3+1) โ
i = 4: nums[4] = 3, expected 5 (4+1) โ
Add 3 to duplicates
i = 5: nums[5] = 2, expected 6 (5+1) โ
Add 2 to duplicates
i = 6: nums[6] = 7, expected 7 (6+1) โ
i = 7: nums[7] = 8, expected 8 (7+1) โ
Duplicates: [3, 2]
Wait, expected [2, 3], got [3, 2]
Order doesn't matter! Both correct! โ
Example 2: nums = [1, 1, 2]
Initial: [1, 1, 2]
Cyclic Sort:
i = 0: nums[0] = 1, correctIndex = 0
Already at correct position! i++
i = 1: nums[1] = 1, correctIndex = 0
nums[1] != nums[0]? 1 != 1? No!
Duplicate! i++
i = 2: nums[2] = 2, correctIndex = 1
nums[2] != nums[1]? 2 != 1? Yes, swap!
[1, 2, 1]
i stays at 2
i = 2: nums[2] = 1, correctIndex = 0
nums[2] != nums[0]? 1 != 1? No!
Duplicate! i++
Final: [1, 2, 1]
Find duplicates:
i = 0: nums[0] = 1, expected 1 โ
i = 1: nums[1] = 2, expected 2 โ
i = 2: nums[2] = 1, expected 3 โ
Add 1 to duplicates
Result: [1] โ
๐ฏ Approach 3: Index as Hash (Most Clever!) โญโญ
Even Better! ๐ก
๐ง The Genius Trick
Use the array ITSELF as a hash table!
Without actually sorting!
Strategy:
For each number num:
Mark index (num-1) as visited by negating it!
If index (num-1) is already negative:
โ We've seen num before โ Duplicate!
Example: [4, 3, 2, 7, 8, 2, 3, 1]
Process 4: Mark index 3 negative
[4, 3, 2, -7, 8, 2, 3, 1]
Process 3: Mark index 2 negative
[4, 3, -2, -7, 8, 2, 3, 1]
Process 2: Mark index 1 negative
[4, -3, -2, -7, 8, 2, 3, 1]
Process 7: Mark index 6 negative
[4, -3, -2, -7, 8, 2, -3, 1]
Process 8: Mark index 7 negative
[4, -3, -2, -7, 8, 2, -3, -1]
Process 2: Check index 1
nums[1] is negative! โ 2 is duplicate! โ
Process 3: Check index 2
nums[2] is negative! โ 3 is duplicate! โ
Process 1: Mark index 0 negative
[-4, -3, -2, -7, 8, 2, -3, -1]
Duplicates: [2, 3] โ
Implementation
/**
* Index as Hash - Mark with Negative
* Time: O(n), Space: O(1)
*/
public List<Integer> findDuplicates(int[] nums) {
List<Integer> duplicates = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
int index = Math.abs(nums[i]) - 1; // Get index to mark
// If already negative, we've seen this number before
if (nums[index] < 0) {
duplicates.add(Math.abs(nums[i]));
} else {
// Mark as visited by negating
nums[index] = -nums[index];
}
}
// Optional: Restore original array
for (int i = 0; i < nums.length; i++) {
nums[i] = Math.abs(nums[i]);
}
return duplicates;
}
โฐ Time: O(n) - Single pass
๐พ Space: O(1) - Modifies in place
โ Fastest! No swapping needed!
๐ Index-as-Hash Dry Run
Example: nums = [4, 3, 2, 7, 8, 2, 3, 1]
Strategy: Mark index (num-1) as negative
Initial: [4, 3, 2, 7, 8, 2, 3, 1]
i = 0: nums[0] = 4
index = |4| - 1 = 3
nums[3] = 7 (positive)
Mark as visited: nums[3] = -7
Array: [4, 3, 2, -7, 8, 2, 3, 1]
i = 1: nums[1] = 3
index = |3| - 1 = 2
nums[2] = 2 (positive)
Mark: nums[2] = -2
Array: [4, 3, -2, -7, 8, 2, 3, 1]
i = 2: nums[2] = -2
index = |-2| - 1 = 1
nums[1] = 3 (positive)
Mark: nums[1] = -3
Array: [4, -3, -2, -7, 8, 2, 3, 1]
i = 3: nums[3] = -7
index = |-7| - 1 = 6
nums[6] = 3 (positive)
Mark: nums[6] = -3
Array: [4, -3, -2, -7, 8, 2, -3, 1]
i = 4: nums[4] = 8
index = |8| - 1 = 7
nums[7] = 1 (positive)
Mark: nums[7] = -1
Array: [4, -3, -2, -7, 8, 2, -3, -1]
i = 5: nums[5] = 2
index = |2| - 1 = 1
nums[1] = -3 (NEGATIVE!)
Already visited! โ 2 is duplicate! โ
Add 2 to duplicates
Array: [4, -3, -2, -7, 8, 2, -3, -1]
i = 6: nums[6] = -3
index = |-3| - 1 = 2
nums[2] = -2 (NEGATIVE!)
Already visited! โ 3 is duplicate! โ
Add 3 to duplicates
Array: [4, -3, -2, -7, 8, 2, -3, -1]
i = 7: nums[7] = -1
index = |-1| - 1 = 0
nums[0] = 4 (positive)
Mark: nums[0] = -4
Array: [-4, -3, -2, -7, 8, 2, -3, -1]
Duplicates: [2, 3] โ
Restore (optional):
Make all positive
Array: [4, 3, 2, 7, 8, 2, 3, 1]
๐ฏ Comparison of All Approaches
Approach Time Space Modifies Notes
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
HashSet O(n) O(n) No Simple
Cyclic Sort O(n) O(1) Yes Pattern practice
Index as Hash O(n) O(1) Yes* Fastest!
* Can restore if needed
Recommended for Interview: Index as Hash (most elegant!)
Recommended for Practice: Cyclic Sort (pattern mastery)
๐ฏ Why Each Approach Works
HashSet Approach:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Simple tracking: If we've seen number before, it's duplicate
Clear and straightforward
Only downside: O(n) space
Cyclic Sort Approach:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Place each number at its correct position
After sorting: duplicates occupy "wrong" positions
Key insight: Number i should be at index i-1
Positions with wrong values reveal duplicates
Index as Hash Approach:
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Use array indices as hash table!
Mark visited by negating value at that index
If index already negative โ seen before โ duplicate!
Genius because:
- No swapping needed
- Single pass
- O(1) space
- Can restore if needed
โ ๏ธ Common Mistakes
Mistake 1: Cyclic sort - forgetting duplicate check
// โ WRONG - Infinite loop on duplicates
while (i < n) {
int correctIdx = nums[i] - 1;
if (nums[i] != nums[correctIdx]) {
swap(nums, i, correctIdx);
}
// Missing i++!
}
// โ CORRECT
if (nums[i] != nums[correctIdx]) {
swap(nums, i, correctIdx);
} else {
i++; // Move past duplicate
}
Mistake 2: Index-as-hash - not using absolute value
// โ WRONG - Negative index!
int index = nums[i] - 1; // If nums[i] negative โ crash!
// โ CORRECT
int index = Math.abs(nums[i]) - 1;
Mistake 3: Not checking if already negative
// โ WRONG - Double negation
nums[index] = -nums[index]; // If already negative โ positive!
// โ CORRECT
if (nums[index] < 0) {
// Found duplicate
} else {
nums[index] = -nums[index];
}
๐ Quick Revision Notes
๐ฏ Core Concept:
Find All Duplicates: Array has [1, n], each once or twice. Three O(1) approaches: Cyclic sort (place at i-1), Index-as-hash (negate to mark), HashSet (O(n) space). Index-as-hash fastest: mark index (num-1) negative, if already negative โ duplicate!
โก Quick Implementation (Index-as-Hash):
import java.util.ArrayList;
import java.util.List;
class Solution {
public List<Integer> findDuplicates(int[] a) {
// Approach - cyclic sort
// key logic - at index i, number i should be present.
// checks: skip when the index where you want to replace is same. Else infinite loop.
int i = 0;
int len = a.length;
List<Integer> res = new ArrayList<>();
while (i < len) {
if(i + 1 != a[i] && a[i] != a[a[i] - 1]) {
swap(a, i, a[i] - 1);
} else {
i++;
}
}
for(i = 0; i < len; i++) {
if(a[i] != i + 1) {
res.add(a[i]);
}
}
return res;
}
private void swap(int[] a, int i, int j) {
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
public static void main(String[] args) {
Solution t = new Solution();
System.out.println(t.findDuplicates(new int[] {4,3,2,7,8,2,3,1}));
System.out.println(t.findDuplicates(new int[] {1,1,2}));
System.out.println(t.findDuplicates(new int[] {1}));
}
}
๐ Key Insights:
- Pattern: Three approaches, all O(n) time
- Index-as-hash: Mark index (num-1) negative
- Already negative: Seen before โ duplicate!
- Use Math.abs: Current value might be negative
- Cyclic sort: Place i at index i-1, wrong positions = duplicates
- Time: O(n), Space: O(1) (except HashSet)
๐ช Memory Aid:
"Mark index negative, if negative โ duplicate!"
Think: "Array IS the hash table!" โจ
๐งช Edge Cases
Case 1: No duplicates
Input: nums = [1]
Output: []
Case 2: All duplicates
Input: nums = [1,1]
Output: [1]
Case 3: Multiple duplicates
Input: nums = [4,3,2,7,8,2,3,1]
Output: [2,3]
All handled correctly! โ
Related Patterns
- Find All Disappeared Numbers (LC 448) - Missing instead of duplicate
- Find Duplicate Number (LC 287) - Single duplicate
- Set Mismatch (LC 645) - One missing, one duplicate
Happy practicing! ๐ฏ
Note: The Index-as-Hash approach is BRILLIANT! Using array indices as a hash table by negating values is a classic technique. This is the most elegant O(1) space solution and impresses in interviews! ๐ชโจ