CF 102697035 - Distinct Numbers
We have a list containing n integers, and the task is to determine how many different values occur at least once. The positions of the values do not matter. For example, the list 2 2 3 5 6 contains the four distinct values 2, 3, 5, and 6, so the answer is 4.
CF 102697035 - Distinct Numbers
Rating: -
Tags: -
Solve time: 51s
Verified: yes
Solution
Problem Understanding
We have a list containing n integers, and the task is to determine how many different values occur at least once. The positions of the values do not matter. For example, the list 2 2 3 5 6 contains the four distinct values 2, 3, 5, and 6, so the answer is 4.
The official problem page gives a 1 second time limit and 256 MB of memory, but the published statement does not specify an upper bound for n or for the integer values in the list. That missing bound means we should choose an algorithm whose running time is linear or close to linear in the number of input values, rather than relying on a quadratic scan. Python can comfortably process hundreds of thousands of simple operations in this setting, while a quadratic algorithm can quickly reach billions of comparisons.
The main edge cases come from repeated values and from values that are not adjacent. Consider 1 followed by 1:
1
1
The correct output is 1. A careless algorithm that counts every position would output 1 here by coincidence, but the distinction becomes visible with 4 copies:
4
7 7 7 7
The correct output is 1, because there is only one distinct value. Counting occurrences instead of distinct values would incorrectly produce 4.
Duplicates can also be separated by other values:
5
1 2 1 3 2
The correct output is 3, since the distinct values are 1, 2, and 3. An approach that only compares neighboring elements would incorrectly treat the second 1 and second 2 as new values.
Finally, every element can already be different:
4
1 2 3 4
The correct output is 4. Any implementation that accidentally removes too many values, or only counts repetitions, can fail on this case.
Approaches
A direct brute-force solution is to examine every value and compare it with all values before it. For each position i, we can scan positions 0 through i - 1 and decide whether the current value has appeared before. If it has not, we increment the answer. This is correct because a value contributes exactly once, at the first position where it appears.
The problem is the number of comparisons. In the worst case, when all values are different, the first value requires no comparison, the second requires one, the third requires two, and so on. The total is
0 + 1 + 2 + ... + (n - 1) = n(n - 1)/2
comparisons. For n = 100000, that is 4,999,950,000 comparisons, far beyond what a 1 second solution can reasonably perform.
The key observation is that we do not actually care where a value appeared or how many times it appeared. We only need a yes-or-no answer to the question, "Have I seen this value already?" A hash set is designed exactly for this operation. While scanning the list once, we insert every previously unseen value into the set. At the end, the size of the set is precisely the number of distinct values.
The brute-force works because explicitly checking earlier positions tells us whether a value has occurred before, but it fails when the list is large because the same history is repeatedly searched. The observation that the history can be summarized by a set lets us replace every repeated scan with an expected constant-time membership check.
| Approach | Time Complexity | Space Complexity | Verdict |
|---|---|---|---|
| Brute Force | O(n²) | O(n) | Too slow for large n |
| Hash Set | O(n) expected | O(n) | Accepted |
Algorithm Walkthrough
- Read
nand thenintegers. We only need to inspect each value once, so there is no reason to preserve any information about its original position. - Create an empty set called
seen. This set represents exactly the values encountered so far. - Scan the values from left to right. If the current value is not in
seen, insert it. A value is inserted only at its first occurrence, so every distinct value contributes exactly one element to the set. - After all values have been processed, print
len(seen). The set contains one element for every different integer in the input, so its size is the required answer.
The invariant is that after processing the first i values, seen contains exactly the distinct values among those first i values. Initially both sets are empty. When the next value has already been seen, the set remains correct. When it is new, adding it makes the set contain exactly the distinct values seen so far. By induction, after the final value the set contains every distinct value in the entire array and nothing else.
Python Solution
import sys
input = sys.stdin.readline
def solve():
n = int(input())
values = list(map(int, input().split()))
seen = set(values)
print(len(seen))
if __name__ == "__main__":
solve()
The first line reads the number of values, and the second line reads the array. The statement places all n values on the next line, so one input() call is sufficient for the official format.
The expression set(values) performs the entire distinct-value operation directly. Python's set is backed by a hash table, so inserting and looking up an integer takes expected O(1) time. The final len operation is also constant time.
There is no need to sort the array. Sorting would also solve the problem in O(n log n), but it performs more work than necessary because the ordering of the distinct values is irrelevant. There is also no need for a frequency dictionary because the number of occurrences of each value is not requested.
The code does not assume that values are positive or bounded by a small constant. Since the published statement only describes them as integers without giving a numeric range, Python's set is a safe representation even if negative values or zero occur.
Worked Examples
For the first sample, the input is:
5
2 2 3 5 6
The scan behaves as follows.
| Current value | seen after processing |
Distinct count |
|---|---|---|
| 2 | {2} |
1 |
| 2 | {2} |
1 |
| 3 | {2, 3} |
2 |
| 5 | {2, 3, 5} |
3 |
| 6 | {2, 3, 5, 6} |
4 |
The second occurrence of 2 does not change the set. The final set has four elements, so the output is 4. This demonstrates why repeated occurrences must contribute only once.
For the second sample, the input is:
4
1 2 3 4
The state evolves as follows.
| Current value | seen after processing |
Distinct count |
|---|---|---|
| 1 | {1} |
1 |
| 2 | {1, 2} |
2 |
| 3 | {1, 2, 3} |
3 |
| 4 | {1, 2, 3, 4} |
4 |
Every value is new, so every step increases the set size. The final answer is 4, confirming that an array with no duplicates contributes one distinct value per position.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) expected | Each of the n values is inserted into a hash set, with expected O(1) insertion. |
| Space | O(n) | In the worst case every input value is different, so the set contains n elements. |
Because the official page gives a 1 second limit but does not publish a maximum value for n, a linear expected-time solution is the appropriate choice. The implementation also uses only one set proportional to the number of distinct values, which stays comfortably within the 256 MB memory limit for typical competitive-programming input sizes.
Test Cases
The official samples are included below. Since the published statement does not specify a maximum n, the large custom case uses 100000 values as a stress test rather than claiming that this is the official maximum.
import sys
import io
def solve():
input = sys.stdin.readline
n = int(input())
values = list(map(int, input().split()))
print(len(set(values)))
def run(inp: str) -> str:
old_stdin = sys.stdin
old_stdout = sys.stdout
try:
sys.stdin = io.StringIO(inp)
sys.stdout = io.StringIO()
solve()
return sys.stdout.getvalue()
finally:
sys.stdin = old_stdin
sys.stdout = old_stdout
# Provided sample 1
assert run("5\n2 2 3 5 6\n") == "4\n", "sample 1"
# Provided sample 2
assert run("4\n1 2 3 4\n") == "4\n", "sample 2"
# Minimum-size input
assert run("1\n42\n") == "1\n", "minimum size"
# All values equal
assert run("6\n9 9 9 9 9 9\n") == "1\n", "all equal"
# Values can repeat non-consecutively and can include zero/negative integers
assert run("7\n-3 0 5 -3 0 8 5\n") == "4\n", "non-consecutive duplicates"
# Large stress case, 100000 distinct values
large_values = list(range(100000))
assert run(
"100000\n" + " ".join(map(str, large_values)) + "\n"
) == "100000\n", "large distinct input"
| Test input | Expected output | What it validates |
|---|---|---|
1 followed by 42 |
1 |
Minimum possible input size |
6 followed by six 9s |
1 |
All values are identical |
7 followed by -3 0 5 -3 0 8 5 |
4 |
Non-consecutive duplicates and general integer values |
100000 followed by 0 through 99999 |
100000 |
Large linear-time stress case |
Edge Cases
For a single-element list, such as
1
42
the set starts empty, receives 42, and ends with size 1. The algorithm does not rely on having a pair of elements to compare, so the smallest valid input works without a special case.
For an array containing only duplicates,
4
7 7 7 7
the first 7 is inserted, while the remaining three values are already present. The final set is {7}, so the output is 1. This directly handles the case where counting positions instead of distinct values would produce the wrong result.
For duplicates that are separated by other values,
5
1 2 1 3 2
the first 1 and first 2 are inserted, the second 1 is ignored, 3 is inserted, and the final 2 is ignored. The resulting set is {1, 2, 3}, giving output 3. This is why a neighboring-element comparison is insufficient.
For an array where every value is different,
4
1 2 3 4
every membership check fails because the value has not appeared before. All four values enter the set, and the answer is 4. This case also represents the worst case for the brute-force method, because every new value would require scanning all previous positions.
The implementation also handles values such as zero and negative integers without modification. For example,
5
-2 0 -2 7 0
produces 3, because the set becomes {-2, 0, 7}. No artificial value range or direct-address array is needed, which makes the solution independent of the unstated numeric bounds in the published problem statement.