Problem overview
The method DistinctValues receives an int[] nums and returns how many different values it contains. Each value counts once, however often it appears.
For [1, 5, -3, 1, -4, 2, -4, 7, 7] the distinct values are 1, 5, -3, -4, 2, 7, so the answer is 6. The array has nine elements, but 1, -4 and 7 each appear twice.
The statement asks for O(n) time and O(n) extra space. Memory is allowed to grow with the input, and that is what makes a fast solution possible.
Approach: remember what you have already seen
The slow way compares every element with every other one, or counts how often each value occurs by scanning the array again and again. That is O(n²). Sorting first and counting where the value changes gives O(n log n) without extra memory. Spending O(n) memory buys a better time.
The faster idea is to keep a HashSet<int> of the values seen so far. A hash set answers "is this value in the set?" in constant time on average, so each element costs one lookup. When a value is not in the set yet, it is new: add it and count it. When it is already there, skip it.
The sorting version works like this: after Array.Sort(nums), equal values sit next to each other, so you count the positions where an element differs from the one before it. It needs no set, but it reorders the caller's array unless you copy it first. The set version leaves nums untouched, which is another reason to prefer it when memory is not a concern.
Step by step
- Create an empty
HashSet<int>calledvalues. It is the memory of everything seen so far. - Create
answer, anintstarting at 0. It counts the distinct values found. - Loop over
numswithforeach. The loop variablevaluetakes each element in turn, and every element is examined once. The order does not matter for counting. - Check
!values.Contains(value). The!flips the answer, so the body runs only the first time a number shows up. - Inside the
if, add the value and increment the counter. Aftervalues.Add(value), every later copy of that number is found in the set and skipped. - Return
answer.
Walking through the example
Take [1, 5, -3, 1, -4], the start of the example, with an empty set and answer = 0:
1is not in the set, so it is added andanswerbecomes 1.5is new, so the set holds{1, 5}andansweris 2.-3is new, soansweris 3.1is already in the set, so nothing changes.-4is new, soansweris 4.
The remaining elements 2, -4, 7, 7 add 2 and 7 and skip the repeats, which brings the total to 6. At every point, answer equals the number of elements in the set.
The solution in C#
class Solution {
public int DistinctValues(int[] nums) {
HashSet<int> values = new HashSet<int>();
int answer = 0;
foreach (int value in nums) {
if (!values.Contains(value)) {
values.Add(value);
answer++;
}
}
return answer;
}
}
Some C# details worth knowing:
- Namespace.
HashSet<T>lives inSystem.Collections.Generic. In a file of your own you needusing System.Collections.Generic;at the top (new projects often include it automatically). Addreturns abool. It returnstruewhen the value was new andfalsewhen the set already held it. A set never stores a value twice.- The shorter form. Because of that return value, the
Containscheck and theAddcan be merged:if (values.Add(value)) answer++;. The lesson keeps them separate so each step is visible. - Even shorter. The counter is redundant, since
values.Countis the number of distinct values at the end. Many C# programmers writenew HashSet<int>(nums).Count, which builds the set straight from the array. The lesson's version practises the loop. - Value equality. For
int, the set compares the numbers themselves, so two equal numbers are always the same entry.
Complexity
Time: O(n). The loop runs n times. In each iteration Contains and Add take constant time on average, so the total is linear.
Space: O(n). In the worst case every element is different and the set holds all n values. The sorting alternative needs no set, but it costs O(n log n) time.
Edge cases and common mistakes
- Empty array. The loop never runs and the method returns
0. - All values equal.
[7, 7, 7]gives1: the first 7 is counted and the others are skipped. - All values different.
[4, 5, 6]gives3, and the set ends up as large as the array. - Negative numbers. They are ordinary values to a hash set.
-4and4are two different entries. - Counting every element. If
answer++sits outside theif, the method returns the length of the array instead of the number of distinct values. - Forgetting
Add. Without it the set stays empty, nothing is ever "seen", and again every element is counted.
Testing
[1, 5, -3, 1, -4, 2, -4, 7, 7]gives6.[7, 7, 7]gives1.[]gives0.