287. Find the Duplicate Number

Given n + 1 integers in [1, n], exactly one value is duplicated. Treat each value as a next pointer and the array becomes an implicit linked list with a cycle — Floyd's algorithm then finds the duplicate in O(n) time and O(1) space without modifying the array.

MediumFloyd's Cycle DetectionTwo PointersImplicit Linked ListTypeScript

PROBLEM What we're solving

You have an array nums of length n + 1 where every integer is in [1, n]. Exactly one number is repeated (possibly more than twice). Return that duplicate without modifying the array and using only O(1) extra space.

Worked example: nums = [1, 3, 4, 2, 2] 2. The value 2 appears at indices 3 and 4, and treating values as pointers causes a cycle: 0→1→3→2→4→2→4→….

KEY IDEA The array is a linked list in disguise

Insight → define a function f(i) = nums[i]. Start a walk at index 0 and follow 0 → nums[0] → nums[nums[0]] → …. Because values are in [1, n]the walk never leaves the array, and because one value is duplicated, two distinct indices point to the same "next" node — which is exactly a cycle whose entranceis the duplicate value. Floyd's two-pointer algorithm locates cycle entrances in O(n) time and O(1) space.

RECIPE Phase 1 → meet, Phase 2 → entrance

  • 0 · Model the walk. Start both slow and fast at index 0. At each step, slow = nums[slow] (1 hop) and fast = nums[nums[fast]] (2 hops).
  • 1 · Phase 1 — find the meeting point. Advance until slow === fast. They are guaranteed to meet inside the cycle because fast laps slow.
  • 2 · Phase 2 — find the cycle entrance. Reset finder = 0. Advance both slow and finder one step at a time. When they meet, that index is the cycle entrance — and the cycle entrance index equals the duplicate value.
  • 3 · Return. return slow (or finder— they're equal).
Classic confusion → students sometimes start slow and fast at index 1 (the first value) instead of index 0. Index 0 is the "outside-the-cycle" node; values in [1, n] guarantee we can never cycle back to 0, so it is the correct head. Starting at 1may put you inside the cycle immediately and break Phase 2's distance argument.

COST Complexity & alternatives

Sort / hash set
O(n log n) / O(n)
Sorting modifies the array; hash set uses O(n) space.
Floyd's cycle
O(n) / O(1)
Two passes, constant extra space, read-only array.

The binary search on value approach counts how many elements are ≤ midand runs in O(n log n) with O(1) space — a valid alternative when Floyd's feels non-obvious. A bit-manipulation XORtrick works only when the duplicate appears exactly twice; Floyd's handles any number of repetitions.

Pattern transfer → the same "implicit linked list" framing appears in Happy Number (detect the cycle where 1 is the entrance), Linked List Cycle II (the canonical version), Circular Array Loop, and any problem where you model a function iteration x → f(x) as a sequence and need to find a repeat.

RUN IT Floyd's cycle: value → index as next pointer

step 0 / 8
STARTArray: [1, 3, 4, 2, 2]. Treat each value as a pointer: nums[i] is the "next" node from index i. Starting both slow and fast at index 0.
1function findDuplicate(nums: number[]): number {
2 // Phase 1 — find the intersection point inside the cycle.
3 // Treat each value as a "next" pointer: nums[i] → nums[nums[i]].
4 let slow = 0;
5 let fast = 0;
6 do {
7 slow = nums[slow];
8 fast = nums[nums[fast]];
9 } while (slow !== fast);
10
11 // Phase 2 — find the entrance to the cycle (= the duplicate).
12 // Reset one pointer to the start; both now move one step at a time.
13 let finder = 0;
14 while (slow !== finder) {
15 slow = nums[slow];
16 finder = nums[finder];
17 }
18 return slow; // entrance of cycle = duplicate value
19}
1031422324
State
0
slow
1
nums[slow]
0
fast
1
nums[fast]
1 — find meeting point
phase
slow pointerfast pointerfinder (phase 2)meeting / duplicate
slowfast

TYPESCRIPT The solution, annotated

findDuplicate.ts
function findDuplicate(nums: number[]): number {
  // Phase 1 — find the intersection point inside the cycle.
  // Treat each value as a "next" pointer: nums[i] → nums[nums[i]].
  let slow = 0;
  let fast = 0;
  do {
    slow = nums[slow];
    fast = nums[nums[fast]];
  } while (slow !== fast);

  // Phase 2 — find the entrance to the cycle (= the duplicate).
  // Reset one pointer to the start; both now move one step at a time.
  let finder = 0;
  while (slow !== finder) {
    slow   = nums[slow];
    finder = nums[finder];
  }
  return slow; // entrance of cycle = duplicate value
}

Reading it block by block

Lines 4–5 — initialise both pointers at index 0. Index 0 is the "pre-cycle head"; values are in [1, n] so no value points back to 0.
Lines 6–9 — Phase 1: slow ×1, fast ×2 until they meet. The do…whileensures at least one iteration (both start equal at 0, but one step separates them). They must meet inside the cycle — Floyd's theorem guarantees it.
Lines 13–16 — Phase 2: move finder from 0, slow from meet point. The mathematical proof shows distance from start → entrance equals distance from meet point → entrance. One step each and they converge at the entrance index.
Line 17 — return slow. At the entrance of the cycle, the index is exactly the duplicate value (because two array cells both hold that index as their nums[i]).
Complexity → O(n) time — Phase 1 runs at most λ + μ steps (cycle length + tail) and Phase 2 at most μ steps, both linear in n. O(1) space — only slow, fast, and finder.

INTERVIEWFollow-ups they'll ask

  • "What if the duplicate can appear more than twice?"Floyd's still works — it only requires that one value is pointed to by multiple indices, which remains true however many times the duplicate appears.
  • "Why can't you just sort?" Sorting modifies the array; the constraint says read-only. In practice, mutating and restoring is fragile in concurrent settings.
  • "Binary search alternative?" Count elements ≤ mid; if the count exceeds mid the duplicate is in [1, mid]. O(n log n) / O(1) — useful when Floyd's feels like magic to the interviewer.
  • "What if there are multiple distinct duplicates?"The problem guarantees exactly one, but if there were multiple you'd need a different approach (e.g., a hash set).
  • "Prove Phase 2 finds the entrance." Let μ = tail length, λ = cycle length. At meeting, slow traveled μ + a·λ and fast μ + b·λ for integers a, b. Their difference is a multiple of λ. Resetting one pointer to 0 and advancing both one step means both reach the entrance after exactly μ more steps.

OPTIMAL Floyd's Cycle Detection

findDuplicate.ts
function findDuplicate(nums: number[]): number {
  // Phase 1 — find the intersection point inside the cycle.
  // Treat each value as a "next" pointer: nums[i] → nums[nums[i]].
  let slow = 0;
  let fast = 0;
  do {
    slow = nums[slow];
    fast = nums[nums[fast]];
  } while (slow !== fast);

  // Phase 2 — find the entrance to the cycle (= the duplicate).
  // Reset one pointer to the start; both now move one step at a time.
  let finder = 0;
  while (slow !== finder) {
    slow   = nums[slow];
    finder = nums[finder];
  }
  return slow; // entrance of cycle = duplicate value
}
Complexity → O(n) time — Phase 1 runs at most λ + μ steps (cycle length + tail) and Phase 2 at most μ steps, both linear in n. O(1) space — only slow, fast, and finder.

ALT 1 Brute force — compare every pair

O(n²) time · O(1) space

For each element, scan the rest of the array for an equal value. It honours the read-only and O(1)-space constraints — it just trades away speed.

approach-2.ts
function findDuplicate(nums: number[]): number {
  for (let i = 0; i < nums.length; i++) {
    for (let j = i + 1; j < nums.length; j++) {
      if (nums[i] === nums[j]) return nums[i]; // found the repeat
    }
  }
  return -1; // unreachable given the problem's guarantee
}
Note → The double loop checks O(n²) pairs, which times out for the large nthe problem allows. Floyd's cycle detection keeps the same O(1) space and read-only array while dropping the time to O(n).

MNEMONIC The one-liner

"Array of n+1 ints in [1,n] = linked list with a cycle. Slow/fast find the knot; reset one to zero and march to the entrance."

TRIGGERS When you see ___ → reach for ___

n+1 integers all in [1, n], find duplicateFloyd's cycle detection
read-only array, O(1) space constrainttwo-pointer implicit linked list
find where a sequence of f(x) repeatsslow ×1 / fast ×2, then reset
"Linked List Cycle II" shapephase 1 meet, phase 2 entrance

SKELETON The reusable shape

skeleton.ts
let slow = 0, fast = 0;
// Phase 1: find meeting point
do {
  slow = nums[slow];
  fast = nums[nums[fast]];
} while (slow !== fast);
// Phase 2: find cycle entrance
let finder = 0;
while (slow !== finder) {
  slow   = nums[slow];
  finder = nums[finder];
}
return slow;

FLASHCARDS Tap to flip

How do you turn the array into an implicit linked list?
Define f(i) = nums[i]. Walking 0 → nums[0] → nums[nums[0]] → … forms a sequence; the duplicate value is pointed to by two different indices, creating a cycle.
tap to flip
1 / 7
Answer to reveal explanations. No penalty for retries.Score: 0 / 9
QUESTION 01
Trace nums = [1, 3, 4, 2, 2]. What does findDuplicate return?
QUESTION 02
What is the time and space complexity of the Floyd's cycle approach?
QUESTION 03
Why does the "next pointer" walk always stay within the array bounds?
QUESTION 04
In Phase 2, why is one pointer reset to index 0 (not to the start of the cycle)?
QUESTION 05
Which of the following constraints is required for Floyd's approach to work here?
QUESTION 06
A colleague suggests using a hash set: if (seen.has(n)) return n; seen.add(n);. What is the main drawback vs. Floyd's?
QUESTION 07
What happens in Phase 1 if you incorrectly use slow = nums[slow] and fast = nums[fast] (both move 1 step)?
QUESTION 08
#287 · Find the Duplicate NumberTreat each value as a 'next' pointer into the array. Floyd's cycle detection finds the meeting point inside the cycle, then a second walk from both the head and the meeting point converges at the duplicate in O(n) time and O(1) space.Which algorithmic approach does this primarily use?
QUESTION 09
#287 · Find the Duplicate NumberTreat each value as a 'next' pointer into the array. Floyd's cycle detection finds the meeting point inside the cycle, then a second walk from both the head and the meeting point converges at the duplicate in O(n) time and O(1) space.Which implementation correctly solves it?