Editorial
Core insight
Find the Duplicate Number · Two Pointers
Core Insight for Find the Duplicate Number
The key intuition required to bridge the gap between an array problem and a Linked List Cycle problem lies in how we interpret the data.
Instead of viewing nums as a simple list of values, view it as a map of pointers where the index i points to the value nums[i].
- Node: The index
i. - Next Pointer: The value
nums[i].
Because the values are restricted to the range [1, n], every value in the array points to a valid index within the array. Since there are n + 1 positions (indices 0 to n) but the values only point to indices 1 through n, index 0 acts as the "head" of our list that is never pointed to by any other node.
Since there is a duplicate number, two different indices point to the same value (the same next index). In graph terms, this means two nodes have edges pointing to the same destination node. This structure creates a cycle. The node that has two incoming edges is the start of the cycle, and its index is the duplicate number.
Visual Description:
Imagine a directed graph where we draw an arrow from index 0 to index nums[0], then from that index to the value stored there, and so on.
- Start at index 0.
- Traverse the path:
- Eventually, because the list is finite and every node has an outgoing edge, you must revisit a node.
- The first node you revisit is the entrance to the cycle. This node corresponds to the duplicate number.
Using Floyd’s Cycle Finding Algorithm (Tortoise and Hare), we can detect this cycle and find its entrance efficiently.
Deep dive
Full breakdown
Full reasoning, pitfalls, and implementation flow
LeetCode 287: Find the Duplicate Number Solution & Explanation
Problem Overview
TL;DR: By treating the array indices and values as nodes and pointers in a linked list, we use Floyd's Cycle Detection (Fast & Slow Pointers) to identify the duplicate number in linear time and constant space.
The LeetCode 287 problem, titled "Find the Duplicate Number," presents an array of integers nums containing n + 1 integers. Each integer falls within the range [1, n]. The problem guarantees that exactly one number is repeated (though it may appear more than twice). The objective is to identify this duplicate number.
Crucially, the solution must adhere to strict constraints:
- Do not modify the original array (e.g., no sorting).
- Use only constant extra space (e.g., no hash sets).
- Solve it in less than time complexity.
Brute Force Approach for Find the Duplicate Number
The most intuitive way to solve this without extra space is to compare every number with every other number to check for duplicates. We can iterate through the array with two nested loops.
Pseudo-code:
for i from 0 to n:
for j from i + 1 to n:
if nums[i] == nums[j]:
return nums[i]Time Complexity Analysis: The time complexity is because for an array of size , we perform roughly comparisons.
Why it fails: While this approach satisfies the space constraint and the "no modification" constraint, it fails on time efficiency. For large inputs (), operations result in operations, which far exceeds the typical limit of operations per second allowed by online judges, leading to a Time Limit Exceeded (TLE) error.
Algorithm Strategy: Two Pointer Patterns
We utilize the Fast & Slow pointer strategy to separate the process into two phases:
-
Intersection Phase:
- Initialize two pointers,
slowandfast, both starting at the head (index 0). - Move
slowone step at a time:slow = nums[slow]. - Move
fasttwo steps at a time:fast = nums[nums[fast]]. - Continue until
slowandfastcollide. They are guaranteed to meet inside the cycle.
- Initialize two pointers,
-
Entrance Finding Phase:
- The meeting point is not necessarily the duplicate number; it is just some node within the cycle.
- To find the entrance (the duplicate), keep one pointer at the meeting point and reset the other pointer to the start (index 0).
- Move both pointers one step at a time.
- The point where they collide is the cycle entrance (the duplicate number).
This strategy maintains constant space because we only store two integer variables (the pointers), and it does not modify the array.
Execution Flow
-
Initialization:
- Set
slow = nums[0]. - Set
fast = nums[0].
- Set
-
Phase 1: Detect Cycle:
- Enter a loop.
- Update
slowtonums[slow]. - Update
fasttonums[nums[fast]]. - Check if
slow == fast. If they are equal, a cycle is detected. Break the loop.
-
Phase 2: Find Duplicate:
- Reset
slowtonums[0](restarting from the head). - Keep
fastat the collision index. - Enter a second loop.
- Check if
slow == fast. If yes, we have found the duplicate. - If no, advance both pointers by one step:
slow = nums[slow],fast = nums[fast]. - Repeat until they match.
- Reset
-
Result:
- Return the value of
slow(orfast) as the duplicate number.
- Return the value of
Proof of Correctness
Let be the distance from the head (index 0) to the cycle entrance. Let be the length of the cycle. Let be the distance from the cycle entrance to the meeting point of the fast and slow pointers.
When the pointers meet:
- The slow pointer has traveled distance .
- The fast pointer has traveled distance (it has looped around the cycle times).
- Since the fast pointer moves at speed, .
Substituting the distances:
The equation implies that the distance from the head to the cycle entrance () is exactly equal to the distance from the meeting point to the cycle entrance () if we wrap around the cycle. Therefore, moving one pointer from the head and one from the meeting point at the same speed ensures they will meet precisely at the cycle entrance.
Pattern Reuse Notes
The Two Pointers - Fast & Slow pattern is a powerful tool for cycle detection and list traversal.
- LeetCode 141: Linked List Cycle: Uses the exact same intersection logic (Phase 1) to determine if a linked list contains a cycle.
- LeetCode 202: Happy Number: Generates a sequence of numbers (sum of squared digits). Fast & Slow pointers are used to detect if the sequence loops endlessly or reaches 1.
- LeetCode 392: Is Subsequence: While primarily a standard Two Pointer problem (one for each string), it shares the fundamental concept of traversing data structures with pointers moving at different rates or conditions.
Understanding LeetCode 287 solidifies your grasp of Floyd's Cycle Detection, making these related problems significantly easier to solve.
Feynman coach · Before you peek at the code
Can you explain the core insight in your own words?
If you can explain it before reading code, you’re far more likely to recall it under interview pressure.
Reference Implementation
C++ Solution for LeetCode 287
1class Solution {
2public:
3 int findDuplicate(vector<int>& nums) {
4 // Initialize pointers based on the first element
5 int slow = nums[0];
6 int fast = nums[0];
7
8 // Phase 1: Find the intersection point of the two runners.
9 do {
10 slow = nums[slow]; // Move slow pointer 1 step
11 fast = nums[nums[fast]]; // Move fast pointer 2 steps
12 } while (slow != fast);
13
14 // Phase 2: Find the "entrance" to the cycle.
15 slow = nums[0]; // Reset slow to start
16 while (slow != fast) {
17 slow = nums[slow]; // Move both 1 step
18 fast = nums[fast];
19 }
20
21 return slow;
22 }
23};Java Solution for LeetCode 287
1class Solution {
2 public int findDuplicate(int[] nums) {
3 // Initialize pointers
4 int slow = nums[0];
5 int fast = nums[0];
6
7 // Phase 1: Detect cycle intersection
8 do {
9 slow = nums[slow]; // Move slow pointer 1 step
10 fast = nums[nums[fast]]; // Move fast pointer 2 steps
11 } while (slow != fast);
12
13 // Phase 2: Find the entrance to the cycle
14 slow = nums[0]; // Reset slow to head
15 while (slow != fast) {
16 slow = nums[slow]; // Move both 1 step
17 fast = nums[fast];
18 }
19
20 return slow;
21 }
22}Python Solution for LeetCode 287
1class Solution:
2 def findDuplicate(self, nums: List[int]) -> int:
3 # Phase 1: Detect cycle intersection
4 slow = nums[0]
5 fast = nums[0]
6
7 while True:
8 slow = nums[slow] # Move slow pointer 1 step
9 fast = nums[nums[fast]] # Move fast pointer 2 steps
10 if slow == fast:
11 break
12
13 # Phase 2: Find the entrance to the cycle
14 slow = nums[0] # Reset slow to head
15 while slow != fast:
16 slow = nums[slow] # Move both 1 step
17 fast = nums[fast]
18
19 return slow