The problem
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.
Examples
Input: nums = [1,3,4,2,2]
Output: 2
Input: nums = [3,1,3,4,2]
Output: 3
Input: nums = [3,3,3,3,3]
Output: 3
Constraints
- 1 <= n <= 10^5
- nums.length == n + 1
- 1 <= nums[i] <= n
- All the integers in nums appear 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?
Explanation
From the description of the problem we learn that we have input of integers nums
that contains n + 1
integers. We also learn that we have only one
repeated number that we need to return.
The brute force way to solve this problem would be to create a hash table and count the number of appearances of every single number and return the number that repeats twice or more times. The time and space complexity for this solution will be O(n)
.
But we can’t solve this problem in a brute force way because we are given constraints that tell us that we must solve this problem without extra space.
Another way to solve this problem is to use a linked list with cycle detection, and Floyd’s algorithm which tells us the beginning of the cycle in the linked list.
Hash Set Solution
func findDuplicate(_ nums: [Int]) -> Int {
var seen: Set<Int> = []
for num in nums {
if seen.contains(num) {
return num
} else {
seen.insert(num)
}
}
return -1
}
Time/ Space complexity
- Time complexity: O(n)
- Space complexity: O(n)
Fast and Slow Pointers Solution
func findDuplicate(_ nums: [Int]) -> Int {
var slow = 0
var fast = 0
while true {
slow = nums[slow]
fast = nums[nums[fast]]
if slow == fast {
break
}
}
var slow2 = 0
while true {
slow = nums[slow]
slow2 = nums[slow2]
if slow == slow2 {
return slow
}
}
}
Explanation
Let’s take a look at an example with input [1, 3, 4, 2, 2]
.
We know that the length of our array is n + 1
and every value in the array will be between 1
and n
.
In our example we have 5
elements, and the range is from 1
to 4
.
Now let’s make some changes. Instead of using our values, let’s use our indices as pointers that will point at some position in our input.
You can see that we have a cycle, and that we can represent our indices as a linked list.
As for our 0
index, none of the other values will be pointing at index 0
because we have a range between 1
and 4
, so 0
is not going to be part of the cycle.
In the picture you can see that multiple nodes are pointing to node 2
.
Our next step will be identifying the beginning of the cycle in our linked list, and it will tell us that the beginning of the cycle is our return value. The way we are going to do it is by applying Floyd’s algorithm.
Let’s look at a slightly different example and visualize the work of Floyd’s algorithm.
- The
fast
andslow
pointers both start at position0
. - The
slow
pointer is going to be shifted byone
each iteration. You can see in the picture that we can make3
jumps using theslow
pointer and3
jumps using thefast
pointer, and you can see that3
is the first position where the two pointers intersect.
Finding the first intersection of two pointers was the first phase of Floyd’s algorithm.
Our next step is to leave our slow
pointer at the intersection. After that, we are going to create a second slow
pointer and put it at the beginning of the array. Then we are going to shift both the first slow
pointer and the second pointer
one more time until they both intersect.
Time/ Space complexity
- Time complexity: O(n)
- Space complexity: O(1)