class Solution:
def findDuplicates(self, nums):
res = []
for num in nums:
idx = abs(num) - 1
if nums[idx] < 0:
res.append(abs(num))
else:
nums[idx] = -nums[idx]
return res
class Solution {
public:
vector<int> findDuplicates(vector<int>& nums) {
vector<int> res;
for (int i = 0; i < nums.size(); ++i) {
int idx = abs(nums[i]) - 1;
if (nums[idx] < 0) {
res.push_back(abs(nums[i]));
} else {
nums[idx] = -nums[idx];
}
}
return res;
}
};
class Solution {
public List<Integer> findDuplicates(int[] nums) {
List<Integer> res = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
int idx = Math.abs(nums[i]) - 1;
if (nums[idx] < 0) {
res.add(Math.abs(nums[i]));
} else {
nums[idx] = -nums[idx];
}
}
return res;
}
}
var findDuplicates = function(nums) {
const res = [];
for (let i = 0; i < nums.length; i++) {
let idx = Math.abs(nums[i]) - 1;
if (nums[idx] < 0) {
res.push(Math.abs(nums[i]));
} else {
nums[idx] = -nums[idx];
}
}
return res;
};
Given an array of integers nums
where each integer is in the range 1
to n
(where n
is the length of the array), some elements appear twice and others appear once. Your task is to find all the elements that appear exactly twice in this array, and return them as a list. You must solve this without using extra space (except for the output list), and in linear time.
nums
appears either once or twice.
At first glance, the simplest way to find duplicates is to use a hash table (such as a Python set
or dict
) to record numbers we've seen so far. As we iterate, if we see a number again, we know it's a duplicate. However, the problem restricts us from using extra space beyond the output list, so we need to find a more efficient approach.
Since all numbers are in the range 1
to n
, we can cleverly use this property to mark which numbers we've seen by modifying the input array in-place. This avoids the need for extra storage, and allows us to detect duplicates by checking the sign of elements at certain indices.
The key insight is to use the value as an index and flip the sign of the number at that index. If we encounter a value whose corresponding index is already negative, that means we've seen this number before.
Here's a step-by-step breakdown of the optimized solution:
nums
: For each number, take its absolute value (since we might have negated it earlier).
idx = abs(num) - 1
).
nums[idx]
is already negative, it means we've seen this number before, so add the absolute value to the result list.
nums[idx]
to mark this value as seen.
This approach leverages the fact that the numbers map directly to indices, and that flipping the sign is a reversible way to mark presence.
Let's take the array [4,3,2,7,8,2,3,1]
as an example.
res = []
.nums[3]
is 7 (positive), so flip to -7. Array becomes [4,3,2,-7,8,2,3,1]
.nums[2]
is 2 (positive), flip to -2. Array: [4,3,-2,-7,8,2,3,1]
.nums[1]
is 3 (positive), flip to -3. Array: [4,-3,-2,-7,8,2,3,1]
.nums[6]
is 3 (positive), flip to -3. Array: [4,-3,-2,-7,8,2,-3,1]
.nums[7]
is 1 (positive), flip to -1. Array: [4,-3,-2,-7,8,2,-3,-1]
.nums[1]
is -3 (negative), so 2 is a duplicate! Add 2 to result.nums[2]
is -2 (negative), so 3 is a duplicate! Add 3 to result.nums[0]
is 4 (positive), flip to -4.
Final result: [2, 3]
, which are the numbers that appear twice.
O(n^2)
if you use nested loops to check for duplicates.O(1)
, but very slow.O(n)
because lookups and insertions are constant time.O(n)
for the hash table.O(n)
since we visit each element once.O(1)
extra space (excluding the output list), because we're only modifying the input array.
By leveraging the constraints of the problem—that all numbers are in the range 1
to n
—we can use the input array itself to track which numbers have been seen by flipping the sign at their corresponding indices. This in-place marking allows us to find all duplicates in a single linear pass, using constant extra space. This solution is both efficient and elegant, and demonstrates how understanding problem constraints can lead to creative optimizations.