A clear explanation of finding the third distinct maximum number using one pass and constant space.
Problem Restatement
We are given an integer array nums.
Return the third distinct maximum number in the array.
If the array does not contain three distinct numbers, return the maximum number.
Duplicates do not count as separate maximums.
The problem statement says to return the third distinct maximum number, or the maximum number if the third maximum does not exist. The constraints include 1 <= nums.length <= 10^4 and -2^31 <= nums[i] <= 2^31 - 1.
Input and Output
| Item | Meaning |
|---|---|
| Input | Integer array nums |
| Output | Third distinct maximum, or maximum if fewer than three distinct values |
| Duplicate rule | Equal values count once |
| Return type | Integer |
Example function shape:
def thirdMax(nums: list[int]) -> int:
...Examples
Example 1:
nums = [3, 2, 1]The distinct values in descending order are:
3, 2, 1The third maximum is:
1So the answer is:
1Example 2:
nums = [1, 2]There are only two distinct values:
2, 1The third maximum does not exist, so we return the maximum:
2Example 3:
nums = [2, 2, 3, 1]The distinct values are:
3, 2, 1The two copies of 2 count as one distinct value.
So the third maximum is:
1First Thought: Sort Unique Values
A simple approach is:
- Convert
numsto a set to remove duplicates. - Sort the distinct values.
- If there are at least three values, return the third largest.
- Otherwise, return the largest.
Code idea:
values = sorted(set(nums))Then:
values[-3]is the third largest if it exists.
This is simple, but sorting costs:
O(n log n)We can do better because we only need the top three distinct values.
Key Insight
We only need to remember three numbers:
| Variable | Meaning |
|---|---|
first | Largest distinct number seen so far |
second | Second largest distinct number seen so far |
third | Third largest distinct number seen so far |
As we scan the array, each new distinct number can affect one of these three positions.
If a number is already equal to one of them, we skip it because duplicates do not count.
If it is larger than first, then all three values shift down:
third = second
second = first
first = numIf it is between first and second, then:
third = second
second = numIf it is between second and third, then:
third = numAlgorithm
Initialize:
first = None
second = None
third = NoneFor each num in nums:
- If
numis already equal tofirst,second, orthird, skip it. - If
firstis empty ornum > first, shift values down and updatefirst. - Else if
secondis empty ornum > second, shiftseconddown and updatesecond. - Else if
thirdis empty ornum > third, updatethird.
At the end:
- If
thirdexists, returnthird. - Otherwise, return
first.
Correctness
After processing any prefix of the array, first, second, and third store the largest three distinct values seen so far, in descending order, when those values exist.
Initially, no values have been processed, so the claim holds.
When a new number is processed, duplicates are skipped. This is correct because equal values do not change the set of distinct maximums.
For a new distinct value, there are four cases.
If it is greater than first, it becomes the largest value, and the old first and second become the second and third values.
If it is between first and second, it becomes the second largest value, and the old second becomes the third.
If it is between second and third, it becomes the third largest value.
If it is smaller than or equal to third, it cannot affect the largest three distinct values.
Thus the invariant remains true after every number.
After the full array is processed, if third exists, it is the third distinct maximum. If it does not exist, fewer than three distinct numbers were found, so the correct answer is the maximum value, first.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n) | We scan the array once |
| Space | O(1) | We store only three values |
Here n is the length of nums.
Implementation
from typing import List, Optional
class Solution:
def thirdMax(self, nums: List[int]) -> int:
first: Optional[int] = None
second: Optional[int] = None
third: Optional[int] = None
for num in nums:
if num == first or num == second or num == third:
continue
if first is None or num > first:
third = second
second = first
first = num
elif second is None or num > second:
third = second
second = num
elif third is None or num > third:
third = num
return third if third is not None else firstCode Explanation
We use None instead of a numeric sentinel:
first = None
second = None
third = NoneThis avoids bugs with the minimum 32-bit integer:
-2**31The duplicate check is necessary:
if num == first or num == second or num == third:
continueWithout this check, an input like:
[2, 2, 3, 1]could incorrectly treat duplicate 2s as different maximums.
When a new largest number appears:
third = second
second = first
first = numThe old values shift down by one rank.
When a number belongs in second place:
third = second
second = numWhen it belongs in third place:
third = numFinally:
return third if third is not None else firstIf we found three distinct values, return the third. Otherwise, return the maximum.
Testing
def test_third_max():
s = Solution()
assert s.thirdMax([3, 2, 1]) == 1
assert s.thirdMax([1, 2]) == 2
assert s.thirdMax([2, 2, 3, 1]) == 1
assert s.thirdMax([1, 2, 2, 5, 3, 5]) == 2
assert s.thirdMax([-1, -2, -3]) == -3
assert s.thirdMax([-2147483648, 1, 2]) == -2147483648
assert s.thirdMax([1, 1, 1]) == 1
print("all tests passed")Test Notes
| Test | Why |
|---|---|
[3,2,1] | Exactly three distinct values |
[1,2] | Fewer than three distinct values |
[2,2,3,1] | Duplicates must be ignored |
[1,2,2,5,3,5] | Mixed duplicates and unordered values |
| Negative values | Works below zero |
| Minimum 32-bit integer | Avoids sentinel-value bug |
| All duplicates | Returns the only maximum |