LeetCode 414 - Third Maximum Number
The problem asks us to find the third distinct maximum number in an integer array. The key word is distinct. Duplicate values should only be counted once when determining rankings. For example, in the array [2,2,3,1], the distinct values are {3,2,1}.
Difficulty: 🟢 Easy
Topics: Array, Sorting
Solution
Problem Understanding
The problem asks us to find the third distinct maximum number in an integer array. The key word is distinct. Duplicate values should only be counted once when determining rankings.
For example, in the array [2,2,3,1], the distinct values are {3,2,1}. Even though 2 appears twice, it only counts as one distinct number. The third distinct maximum is therefore 1.
If the array contains fewer than three distinct values, we return the overall maximum number instead. For example, in [1,2], there are only two distinct numbers, so the answer is the largest value, 2.
The input is an array of integers named nums. The output is a single integer representing either:
- the third distinct maximum value, or
- the maximum value if fewer than three distinct values exist
The constraints tell us several important things:
- The array length can be as large as
10^4 - Values can range from
-2^31to2^31 - 1 - Negative numbers are valid inputs
- Duplicate values are common and must be handled carefully
The follow-up specifically asks for an O(n) solution, which means we should avoid sorting if possible. Sorting takes O(n log n) time, which is acceptable for the constraints but not optimal according to the follow-up requirement.
Several edge cases are important:
- Arrays with fewer than three distinct numbers
- Arrays where all numbers are identical
- Arrays containing negative values
- Arrays containing the minimum possible integer value
- Arrays with many duplicates that could incorrectly inflate the count of maximum values
A naive implementation can easily fail if it counts duplicates separately or mishandles extreme integer values.
Approaches
Brute Force Approach
The most straightforward solution is to remove duplicates, sort the remaining numbers in descending order, and then return either:
- the third element if at least three distinct numbers exist
- the first element otherwise
For example:
- Input:
[2,2,3,1] - Distinct values:
[2,3,1] - Sorted descending:
[3,2,1] - Third distinct maximum:
1
This approach is easy to understand and guarantees correctness because sorting places all distinct values in order.
However, sorting requires O(n log n) time, which does not satisfy the follow-up requirement asking for O(n) complexity.
Optimal Approach
The key insight is that we do not need the full sorted order of the array. We only care about the top three distinct values.
Instead of sorting everything, we can scan through the array once while maintaining:
- the largest distinct value
- the second largest distinct value
- the third largest distinct value
Whenever we encounter a new number:
- Ignore it if it is already one of the tracked maximums
- Update the tracked values appropriately if it belongs in the top three
This works because at any point during traversal, we maintain the invariant that the three variables always contain the three largest distinct numbers seen so far.
Since each number is processed exactly once, the algorithm runs in linear time.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n log n) | O(n) | Remove duplicates and sort descending |
| Optimal | O(n) | O(1) | Track top three distinct values during one pass |
Algorithm Walkthrough
- Initialize three variables named
first,second, andthirdto represent the largest, second largest, and third largest distinct numbers seen so far. Initially, all are unset. - Iterate through each number in the array.
- Before updating anything, check whether the current number is already equal to one of the tracked maximums. If it is, skip it because duplicates should not count multiple times.
- If the current number is larger than
first, shift the existing values downward:
thirdbecomessecondsecondbecomesfirstfirstbecomes the current number
- Otherwise, if the number is smaller than
firstbut larger thansecond, update:
thirdbecomessecondsecondbecomes the current number
- Otherwise, if the number is smaller than both
firstandsecondbut larger thanthird, updatethird. - After processing all numbers:
- If
thirdexists, return it - Otherwise, return
first
We use only three variables because the problem only requires the third distinct maximum. Tracking anything beyond the top three would be unnecessary.
Why it works
At every iteration, the variables first, second, and third store the three largest distinct values encountered so far in descending order. Duplicate values are skipped, so each tracked value remains distinct. Since every number is examined exactly once and correctly inserted into its position among the top three, the final values after traversal represent the true top three distinct maximums in the array.
Python Solution
from typing import List
class Solution:
def thirdMax(self, nums: List[int]) -> int:
first = second = third = None
for num in nums:
# Skip duplicates
if num == first or num == second or num == third:
continue
# Update first maximum
if first is None or num > first:
third = second
second = first
first = num
# Update second maximum
elif second is None or num > second:
third = second
second = num
# Update third maximum
elif third is None or num > third:
third = num
return third if third is not None else first
The implementation follows the algorithm directly.
The variables first, second, and third track the top three distinct values. They are initialized to None because the array may contain negative numbers, including the minimum possible integer value. Using None avoids needing special sentinel values.
The duplicate check is extremely important. Without it, repeated values could incorrectly occupy multiple ranking positions.
When a new largest value is found, the existing maximums shift downward to preserve ordering. The same logic applies when updating the second maximum.
At the end, if third is still None, it means fewer than three distinct values exist, so we return the maximum value stored in first.
Go Solution
package main
import "math"
func thirdMax(nums []int) int {
first := math.MinInt64
second := math.MinInt64
third := math.MinInt64
hasFirst := false
hasSecond := false
hasThird := false
for _, num := range nums {
// Skip duplicates
if (hasFirst && num == first) ||
(hasSecond && num == second) ||
(hasThird && num == third) {
continue
}
// Update first maximum
if !hasFirst || num > first {
third = second
hasThird = hasSecond
second = first
hasSecond = hasFirst
first = num
hasFirst = true
} else if !hasSecond || num > second {
// Update second maximum
third = second
hasThird = hasSecond
second = num
hasSecond = true
} else if !hasThird || num > third {
// Update third maximum
third = num
hasThird = true
}
}
if hasThird {
return third
}
return first
}
The Go implementation uses explicit boolean flags because Go integers cannot naturally represent an uninitialized state like Python's None.
We use math.MinInt64 as a placeholder initial value, but the boolean flags determine whether a value is actually valid. This avoids issues when the input itself contains the minimum integer value.
The rest of the logic mirrors the Python solution exactly.
Worked Examples
Example 1
Input: nums = [3,2,1]
| Step | Current Number | first | second | third |
|---|---|---|---|---|
| Start | - | None | None | None |
| 1 | 3 | 3 | None | None |
| 2 | 2 | 3 | 2 | None |
| 3 | 1 | 3 | 2 | 1 |
Since third exists, return 1.
Example 2
Input: nums = [1,2]
| Step | Current Number | first | second | third |
|---|---|---|---|---|
| Start | - | None | None | None |
| 1 | 1 | 1 | None | None |
| 2 | 2 | 2 | 1 | None |
third does not exist, so return first = 2.
Example 3
Input: nums = [2,2,3,1]
| Step | Current Number | Action | first | second | third |
|---|---|---|---|---|---|
| Start | - | Initialize | None | None | None |
| 1 | 2 | New first max | 2 | None | None |
| 2 | 2 | Duplicate, skip | 2 | None | None |
| 3 | 3 | Shift down | 3 | 2 | None |
| 4 | 1 | New third max | 3 | 2 | 1 |
Return 1.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each element is processed exactly once |
| Space | O(1) | Only three tracking variables are used |
The algorithm performs a single pass through the array, making the runtime linear in the number of elements. No auxiliary data structures proportional to input size are required, so the space complexity remains constant.
Test Cases
from typing import List
class Solution:
def thirdMax(self, nums: List[int]) -> int:
first = second = third = 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 first
sol = Solution()
assert sol.thirdMax([3, 2, 1]) == 1 # basic case with exactly 3 distinct values
assert sol.thirdMax([1, 2]) == 2 # fewer than 3 distinct values
assert sol.thirdMax([2, 2, 3, 1]) == 1 # duplicates should not count twice
assert sol.thirdMax([1, 1, 1]) == 1 # all values identical
assert sol.thirdMax([5]) == 5 # single element array
assert sol.thirdMax([-1, -2, -3]) == -3 # negative numbers
assert sol.thirdMax([1, 2, 2, 5, 3, 5]) == 2 # mixed duplicates
assert sol.thirdMax([2, 2, 2, 3]) == 3 # only two distinct values
assert sol.thirdMax([-2147483648, 1, 2]) == -2147483648 # minimum int value
assert sol.thirdMax([10, 9, 8, 7, 6]) == 8 # descending order
assert sol.thirdMax([1, 2, 3, 4, 5]) == 3 # ascending order
| Test | Why |
|---|---|
[3,2,1] |
Standard case with exactly three distinct values |
[1,2] |
Fewer than three distinct numbers |
[2,2,3,1] |
Duplicate handling |
[1,1,1] |
All numbers identical |
[5] |
Smallest possible array |
[-1,-2,-3] |
Negative number handling |
[1,2,2,5,3,5] |
Multiple duplicates mixed with unique values |
[2,2,2,3] |
Only two distinct values despite many duplicates |
[-2147483648,1,2] |
Extreme integer boundary |
[10,9,8,7,6] |
Descending input |
[1,2,3,4,5] |
Ascending input |
Edge Cases
Arrays With Fewer Than Three Distinct Values
A common mistake is assuming the array always contains at least three unique numbers. For example, [1,2] only has two distinct values. The correct behavior is to return the maximum value, not produce an error or incorrect third value.
The implementation handles this by checking whether third was ever assigned. If not, it returns first.
Arrays With Many Duplicates
Duplicates can easily break naive solutions. In [2,2,3,1], the value 2 appears twice but should count only once in the ranking.
The implementation explicitly skips any number already equal to first, second, or third. This guarantees that only distinct values are tracked.
Arrays Containing Extreme Integer Values
The constraints allow values as small as -2^31. Using sentinel values carelessly can create subtle bugs when the sentinel itself appears in the input.
The Python solution avoids this entirely by using None for uninitialized values. The Go solution uses boolean flags to distinguish valid values from placeholder initialization values.