Problem overview
The method FindDuplicates receives an int[] nums and returns a new int[] containing every value that appears more than once. Each duplicated value is listed once, and the result may be in any order.
For [2, 3, 1, 1, 4, 3, 2, 1] the values 1, 2 and 3 repeat, while 4 appears only once. The answer is those three numbers: [2, 1, 3] is one valid order. Note that 1 occurs three times but must appear in the result only once.
The statement asks for O(n) time and O(n) extra space.
Approach: count as you go
Comparing every pair of elements would find the duplicates in O(n²). A faster way is to remember how many times each value has appeared so far. A Dictionary<int, int> does that: the key is the number, and the value is its count. Lookups and updates take constant time on average, so one pass over the array is enough.
The key observation is when to record a duplicate. The moment a value's count reaches exactly 2, you know it is a duplicate, and you add it to the result. A third or fourth copy raises the count to 3 or 4, which never equals 2 again, so the value is not added twice. Testing for "greater than 1" instead would add it on every later copy.
On the example, the counts evolve like this. After reading 2, 3, 1 every count is 1. The next 1 raises its count to 2, so 1 goes into the result. Then 4 arrives with count 1, the second 3 reaches 2 and is added, and the second 2 reaches 2 and is added. The last 1 raises its count to 3, which is ignored. The result is [1, 3, 2], the same three values in order of their second appearance.
Step by step
- Create
result, aList<int>. The number of duplicates is unknown in advance, and a list can grow, so it is the right place to collect them. - Create
freq, aDictionary<int, int>. It maps each number to how many times it has been seen. - Loop over
numswithforeach, usingvalueas the loop variable, so every number is counted. - Increase the count.
freq.GetValueOrDefault(value, 0)reads the old count, or0when the value has no entry yet. Add 1 and store the sum back withfreq[value] = .... - Test
freq[value] == 2. This is the exact moment the value becomes a known duplicate. - Add it to
result. Every duplicate is recorded once, however many copies follow. - Return
result.ToArray(). The method's return type isint[], so the list is converted.
The solution in C#
class Solution {
public int[] FindDuplicates(int[] nums) {
List<int> result = new List<int>();
Dictionary<int, int> freq = new Dictionary<int, int>();
foreach (int value in nums) {
freq[value] = freq.GetValueOrDefault(value, 0) + 1;
if (freq[value] == 2) {
result.Add(value);
}
}
return result.ToArray();
}
}
The C# details behind these lines:
- Missing keys. Reading
freq[value]for a key that is not in the dictionary throws aKeyNotFoundException. Assigning tofreq[value]is different: it adds the key when it is missing and replaces the value when it exists. The linefreq[value] = freq.GetValueOrDefault(value, 0) + 1;relies on both facts. The read side usesGetValueOrDefaultso it never throws, and the write side creates the entry. - The next line is safe. By the time
freq[value] == 2runs, the assignment above has created the key, so the plain read cannot fail. TryGetValueis the other common pattern.freq.TryGetValue(value, out int count)returnsfalsefor a missing key and leavescountat 0. Both styles are fine;GetValueOrDefaultis shorter.List<T>versus arrays. An array has a fixed length, while a list grows as you callAdd. Collecting into a list and converting once at the end is the usual way to build an array whose size is not known up front.ToArray()on a list.List<T>has its ownToArray()method, so this call needs nousing System.Linq;. The generic collections themselves needusing System.Collections.Generic;.
Complexity
Time: O(n). Each element causes a constant number of dictionary operations (on average) and at most one list append.
Space: O(n). The dictionary holds one entry per distinct value, which can be n entries, and the result list holds at most half of them.
Edge cases and common mistakes
- Empty array or one element. No count reaches 2, so the result is an empty array.
- No duplicates.
[1, 2, 3]returns an empty array. - A value repeated many times.
[5, 5, 5, 5]returns[5], not four copies. - Using
>= 2or> 1. That adds the value again for each extra copy, so the result contains repeats. - Reading before writing. Writing
freq[value]++on a missing key throwsKeyNotFoundException, because++reads first. Use theGetValueOrDefaultline, or checkContainsKeyfirst. - Depending on the order. The result lists values by their second appearance. The statement allows any order, so a correct solution may differ from the example output.
Testing
[2, 3, 1, 1, 4, 3, 2, 1]gives[1, 3, 2]with this solution, which contains the same values as the example's[2, 1, 3].[5, 5, 5, 5]gives[5].[1, 2, 3]gives[].