Find the Duplicate Number
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given an array nums of n + 1 integers, each between 1 and n inclusive. By the pigeonhole principle at least one value repeats; the problem guarantees that exactly one value repeats, though it may appear more than twice. Return that value.
The full version adds two restrictions that shape the solution: do not modify the array, and use only constant extra space.
Constraints: 1 ≤ n ≤ 10^5, nums.length == n + 1, 1 ≤ nums[i] ≤ n.
Example 1
nums = [1, 3, 4, 2, 2]
answer = 2
Example 2
nums = [3, 3, 3, 3, 3]
answer = 3 // the duplicate may occur many times
Approach 1: Brute force, sort a copy
Intuition. After sorting, equal values sit next to each other, so the duplicate is the first value equal to its left neighbour. Sorting in place would modify the input, so we sort a copy, which breaks the space restriction instead. Comparing every pair would avoid the copy, but at O(n^2) time.
Algorithm.
- Copy and sort the array.
- Return the first
a[i]witha[i] == a[i - 1].
static int bruteForce(int[] nums) {
int[] a = nums.clone();
Arrays.sort(a);
for (int i = 1; i < a.length; i++) {
if (a[i] == a[i - 1]) return a[i];
}
return -1;
}
Complexity. Time O(n log n) for the sort. Space O(n) for the copy.
Approach 2: Better, remember what you have seen
Intuition. Scan once and mark every value in a lookup table. The first value that is already marked is the duplicate. Because values lie in 1..n, a boolean array of size n + 1 works as the table; a HashSet would work for arbitrary values.
Algorithm.
- Create
seenof sizen + 1. - For each value
v: ifseen[v], returnv; otherwise setseen[v] = true.
static int better(int[] nums) {
boolean[] seen = new boolean[nums.length]; // values are in [1, n] and nums.length = n + 1
for (int v : nums) {
if (seen[v]) return v;
seen[v] = true;
}
return -1;
}
Complexity. Time O(n), a single pass. Space O(n) for the table, which violates the constant-space requirement.
A tempting fix is to store the marks inside nums itself, for example by negating nums[v]. That is O(1) extra space, but it modifies the array, which the problem forbids.
Approach 3: Optimal, Floyd’s cycle detection
Intuition. Read the array as a linked list: index i points to index nums[i]. Every index 0..n has exactly one outgoing pointer, and every pointer lands on a valid index because values are in 1..n.
Start walking from index 0. No value is 0, so nothing points back to index 0: it is the head of the list, never inside a loop. Since there are only n + 1 indices, the walk must eventually revisit one, so the path has the shape of the Greek letter rho: a tail leading into a cycle.
Now look at the node where the tail enters the cycle. It has two incoming pointers: one from the last tail node and one from the last cycle node. Two different indices i and j with nums[i] == nums[j] equal to that node’s index means the node’s index is the duplicated value. So the problem becomes: find the entrance of the cycle in a linked list, using O(1) space. That is exactly what Floyd’s tortoise-and-hare algorithm does.
Phase 1, meet inside the cycle. Move slow one step and fast two steps at a time. Once both are in the cycle, the gap between them shrinks by one each step, so they must meet.
Phase 2, find the entrance. Let a be the tail length and c the cycle length. At the meeting point, slow has taken k steps and fast 2k, and the extra k steps are whole laps, so k is a multiple of c. If slow now goes back to the head while fast stays, and both move one step at a time, after a steps the restarted pointer is at the entrance, and the other has made k + a steps in total, which is a steps plus whole laps: also the entrance. They meet exactly there.
Algorithm.
slow = fast = 0. Repeatslow = nums[slow],fast = nums[nums[fast]]until they are equal.- Reset
slow = 0. Move both one step at a time until they are equal. - Return
slow.
static int optimal(int[] nums) {
int slow = 0, fast = 0; // index 0 is the head: no value points back to it
do {
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow != fast);
slow = 0; // phase 2: both move one step until they meet
while (slow != fast) {
slow = nums[slow];
fast = nums[fast];
}
return slow; // the cycle entrance is the duplicated value
}
Dry run on nums = [1, 3, 4, 2, 2]. The list is 0 → 1 → 3 → 2 → 4 → 2 → ..., a tail 0, 1, 3 and the cycle 2 → 4 → 2.
| phase | step | slow | fast |
|---|---|---|---|
| 1 | start | 0 | 0 |
| 1 | 1 | 1 | 3 |
| 1 | 2 | 3 | 4 |
| 1 | 3 | 2 | 4 |
| 1 | 4 | 4 | 4 (meet) |
| 2 | start | 0 | 4 |
| 2 | 1 | 1 | 2 |
| 2 | 2 | 3 | 4 |
| 2 | 3 | 2 | 2 (meet) |
They meet at index 2, the duplicated value.
Complexity. Time O(n): phase 1 ends within a bounded number of laps after slow enters the cycle, and phase 2 takes a ≤ n steps. Space O(1). The array is only read, never written.
Another approach that satisfies both restrictions is binary search on the value: for a candidate x, count how many elements are ≤ x. If the count exceeds x, the duplicate is at most x. That costs O(n log n) time, so Floyd’s method is preferred.
Edge cases and pitfalls
- Starting at index 0. It is the natural head because no value points to it. Starting phase 1 at
nums[0]also works, but then phase 2 must restart fromnums[0]too; mixing the two conventions gives wrong answers. - Duplicate appearing many times.
[3, 3, 3, 3, 3]still yields a single cycle entrance, so Floyd’s method needs no change. Approaches based on sum formulas, such assum(nums) - n(n+1)/2, fail here. - Smallest input.
[1, 1]gives the list0 → 1 → 1, and both phases finish at once. - Do not modify the input. Sorting in place or negative marking is fine only if the problem allows it. The Java tests check that the array is unchanged after every approach.
- Out-of-range values. Floyd’s method relies on every value being a valid index in
1..n; with arbitrary values, use the hash set.
Related problems
- Find the starting point in LL is the same Floyd phase 2 on an actual linked list.
- Detect a loop in LL is phase 1 on its own.
- Find the repeating and missing number changes the setup: one value repeats and another goes missing.
- Find missing number is the complementary question solved with sums or XOR.
- Contains Duplicate asks only whether any repeat exists, with arbitrary values.