Find the Duplicate Number
Problem
https://leetcode.com/problems/find-the-duplicate-number/
Given an array of integers nums containing n + 1 integers where
each integer is in the range [1, n] inclusive.
There is only one repeated number in nums, return
this repeated number.
You must solve the problem without modifying the array nums and
using only constant extra space.
Example 1:
Input: nums = [1,3,4,2,2]
Output: 2
Example 2:
Input: nums = [3,1,3,4,2]
Output: 3
Example 3:
Input: nums = [3,3,3,3,3]
Output: 3
Constraints:
1 <= n <= 10:sup:`5`nums.length == n + 11 <= nums[i] <= nAll the integers in
numsappear only once except for precisely one integer which appears two or more times.
Follow up:
How can we prove that at least one duplicate number must exist in
nums?Can you solve the problem in linear runtime complexity?
Pattern
Array, Two Pointers, Binary Search, Bit Manipulation
Approaches
Code
def findDuplicate(nums: list[int]) -> int:
"""Find the duplicated number in ``nums``."""
for i in range(len(nums)):
n = abs(nums[i])
if nums[n] < 0:
return n
else:
nums[n] = -nums[n]
return -1
Test
>>> from find_the_duplicate_number__negation_marking import findDuplicate
>>> findDuplicate([1, 3, 4, 2, 2])
2
>>> findDuplicate([3, 1, 3, 4, 2])
3
>>> findDuplicate([3, 3, 3, 3, 3])
3
- find_the_duplicate_number__negation_marking.findDuplicate(nums: list[int]) int
Find the duplicated number in
nums.
Explanation
We can view the array as a linked list by starting at i = 0 then setting
to i = nums[i]. Because one number is duplicated, two indices will
point into the same chain, creating a cycle.
Floyd’s Cycle Detection uses 2 pointers slow and fast which move
one step at a time and two steps at a time respectively. If there’s a
cycle, they will eventually meet somewhere in the cycle.
To find the duplicate number where the 2 chains meet, we start a new pointer from the beginning, moving both pointers one step at a time. Where they meet again is the duplicate number (the entry point of the cycle).
To see this, let \(L\) be the distance from the start to the cycle
entrance, \(C\) the cycle length, and \(k\) the distance from the
entrance to where slow and fast first met. Since fast traveled
twice as far as slow, the extra distance is a whole number of laps:
\(L + k = mC\), so \(L = mC - k\). Walking \(L\) steps from the
meeting point is walking back \(k\) steps to the entrance plus
\(m\) full laps, landing on the entrance. The new pointer reaches the
entrance after exactly \(L\) steps by definition, so the two pointers
meet there.
Code
def findDuplicate(nums: list[int]) -> int:
"""Find the duplicate number using Floyd's cycle detection."""
slow = 0
fast = 0
while True:
slow = nums[slow]
fast = nums[nums[fast]]
if slow == fast:
break
slow2 = 0
while slow2 != slow:
slow = nums[slow]
slow2 = nums[slow2]
return slow2
Test
>>> from find_the_duplicate_number__cycle_detection import findDuplicate
>>> findDuplicate([1, 3, 4, 2, 2])
2
>>> findDuplicate([3, 1, 3, 4, 2])
3
>>> findDuplicate([3, 3, 3, 3, 3])
3
Complexity
\(n\) is the number of elements in nums
Measure |
Complexity |
Notes |
|---|---|---|
Time |
\(O(n)\) |
slow and fast pointers converge in at most \(L + C\) steps and finding the entrance takes exactly \(L\) steps which are both bounded by \(n\) |
Auxiliary Space |
\(O(1)\) |
- find_the_duplicate_number__cycle_detection.findDuplicate(nums: list[int]) int
Find the duplicate number using Floyd’s cycle detection.