LeetCode 3731 - Find Missing Elements
This problem asks us to reconstruct a missing sequence of integers from a partially incomplete array. We are given an integer array nums containing unique values, and we know an important fact: the array originally contained every integer in a continuous range, but some…
Difficulty: 🟢 Easy
Topics: Array, Hash Table, Sorting
Solution
Problem Understanding
This problem asks us to reconstruct a missing sequence of integers from a partially incomplete array.
We are given an integer array nums containing unique values, and we know an important fact: the array originally contained every integer in a continuous range, but some numbers were removed. Even though some values are missing, the smallest and largest values of the original range are guaranteed to still exist in the array.
Our task is to return a sorted list of all missing integers between the minimum and maximum numbers in the array.
In simpler terms, we want to:
- Find the smallest number in
nums - Find the largest number in
nums - Generate every integer in that inclusive range
- Identify which numbers are absent from
nums - Return those missing numbers in ascending order
For example, if nums = [1,4,2,5], the minimum is 1 and the maximum is 5. The complete original range must have been:
[1,2,3,4,5]
Since 3 is missing from nums, we return:
[3]
The constraints are very small:
2 <= nums.length <= 1001 <= nums[i] <= 100- All integers are unique
These limits tell us that performance is not especially demanding. Even a less efficient solution would likely pass. However, since this is an interview-style problem, we should still think about designing a clean and efficient approach.
Several guarantees simplify the implementation:
- The array always contains at least two numbers.
- Numbers are unique, so we do not need to handle duplicates.
- The smallest and largest values are always present, meaning the range boundaries are reliable.
- The result must be sorted, but iterating from minimum to maximum naturally gives us sorted output.
There are a few edge cases worth recognizing early. If the array already contains every number in the range, we should return an empty list. If the input contains only the minimum and maximum values, then everything in between is missing. Another subtle case is when the array is unsorted, which means we cannot assume the first or last element defines the range.
Approaches
Brute Force Approach
A straightforward approach is to first sort the array, then inspect gaps between consecutive numbers.
After sorting, we compare adjacent values. If the difference between two neighboring numbers is greater than 1, then all integers in between are missing and should be added to the result.
For example:
[1,4,2,5]
After sorting:
[1,2,4,5]
We compare adjacent elements:
- Between
1and2, nothing is missing - Between
2and4, number3is missing - Between
4and5, nothing is missing
This method works because sorting places numbers in natural order, making gaps easy to identify.
However, sorting introduces an O(n log n) time cost, which is unnecessary because we only care whether numbers exist in a range.
Optimal Approach
The key observation is that we only need to determine whether each integer between the minimum and maximum exists in the array.
A hash set is ideal for fast membership checks.
We first compute:
minimum = min(nums)maximum = max(nums)
Then we place all values into a set for O(1) average lookup time.
Next, we iterate through every integer from minimum to maximum. If a number is not present in the set, we add it to the result.
Since we scan the range in ascending order, the output is automatically sorted.
This avoids sorting entirely and produces a cleaner, more direct solution.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n log n) | O(1) or O(n) | Sort array and detect gaps between adjacent numbers |
| Optimal | O(n + r) | O(n) | Use a hash set for constant time membership checks, where r is the range size |
Here, r = max(nums) - min(nums).
Algorithm Walkthrough
Optimal Algorithm
- Find the smallest and largest values in the array.
These define the boundaries of the original continuous range. Since the problem guarantees both are still present, we can safely use them. 2. Convert the array into a hash set.
A set allows constant average time membership checking. Instead of repeatedly scanning the array to see whether a number exists, we can check instantly. 3. Iterate through every integer from the minimum to the maximum value.
This reconstructs the original range one number at a time. 4. For each integer, check whether it exists in the set.
If the number is missing, append it to the result list. 5. Return the result list.
Because we iterate in increasing order, the missing numbers are naturally sorted.
Why it works
The algorithm works because every valid number from the original range must lie between the minimum and maximum values. By checking each integer in this interval exactly once, we guarantee that every missing number is discovered. Since the set contains exactly the numbers present in nums, any value absent from the set must be missing from the original sequence.
Python Solution
from typing import List
class Solution:
def findMissingElements(self, nums: List[int]) -> List[int]:
minimum = min(nums)
maximum = max(nums)
existing_numbers = set(nums)
missing_numbers = []
for number in range(minimum, maximum + 1):
if number not in existing_numbers:
missing_numbers.append(number)
return missing_numbers
The implementation begins by identifying the minimum and maximum values in the array. These determine the original range of integers.
Next, we convert nums into a set called existing_numbers. This is important because membership checks in a set are extremely efficient, usually O(1).
We then iterate through every number in the inclusive range [minimum, maximum]. Whenever a number is absent from the set, we add it to missing_numbers.
Finally, we return the result list. Since numbers are checked in increasing order, the output is already sorted and no additional sorting step is necessary.
Go Solution
func findMissingElements(nums []int) []int {
minimum := nums[0]
maximum := nums[0]
// Find minimum and maximum
for _, num := range nums {
if num < minimum {
minimum = num
}
if num > maximum {
maximum = num
}
}
// Build hash set
existingNumbers := make(map[int]bool)
for _, num := range nums {
existingNumbers[num] = true
}
// Find missing numbers
missingNumbers := []int{}
for number := minimum; number <= maximum; number++ {
if !existingNumbers[number] {
missingNumbers = append(missingNumbers, number)
}
}
return missingNumbers
}
The Go implementation follows the same logic as the Python version. Since Go does not have built-in min() and max() functions for slices, we manually scan the array to compute them.
Instead of a Python set, Go uses a map[int]bool to simulate hash set behavior. Presence checking remains efficient with average O(1) lookups.
The result is initialized as an empty slice rather than nil, ensuring a clean empty return value when no integers are missing.
Worked Examples
Example 1
Input:
nums = [1,4,2,5]
First, determine the range:
| Variable | Value |
|---|---|
| minimum | 1 |
| maximum | 5 |
Create the set:
{1,2,4,5}
Iterate through the range:
| Current Number | In Set? | Result |
|---|---|---|
| 1 | Yes | [] |
| 2 | Yes | [] |
| 3 | No | [3] |
| 4 | Yes | [3] |
| 5 | Yes | [3] |
Final answer:
[3]
Example 2
Input:
nums = [7,8,6,9]
Range:
| Variable | Value |
|---|---|
| minimum | 6 |
| maximum | 9 |
Set:
{6,7,8,9}
Iteration:
| Current Number | In Set? | Result |
|---|---|---|
| 6 | Yes | [] |
| 7 | Yes | [] |
| 8 | Yes | [] |
| 9 | Yes | [] |
Final answer:
[]
Example 3
Input:
nums = [5,1]
Range:
| Variable | Value |
|---|---|
| minimum | 1 |
| maximum | 5 |
Set:
{1,5}
Iteration:
| Current Number | In Set? | Result |
|---|---|---|
| 1 | Yes | [] |
| 2 | No | [2] |
| 3 | No | [2,3] |
| 4 | No | [2,3,4] |
| 5 | Yes | [2,3,4] |
Final answer:
[2,3,4]
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n + r) | Building the set takes O(n), scanning the range takes O(r) |
| Space | O(n) | The hash set stores all elements from nums |
Here, n is the number of elements in nums, and r = max(nums) - min(nums).
The algorithm performs one pass to build the hash set and another pass through the numerical range. Since constraints are small, this is highly efficient. In the worst case, the range is at most 100, making the solution effectively constant time for this problem size.
Test Cases
solution = Solution()
# Provided examples
assert solution.findMissingElements([1, 4, 2, 5]) == [3] # single missing number
assert solution.findMissingElements([7, 8, 6, 9]) == [] # no missing numbers
assert solution.findMissingElements([5, 1]) == [2, 3, 4] # only endpoints exist
# Boundary values
assert solution.findMissingElements([1, 2]) == [] # smallest valid input, consecutive
assert solution.findMissingElements([1, 100]) == list(range(2, 100)) # maximum range
# Unsorted input
assert solution.findMissingElements([10, 7, 8, 5]) == [6, 9] # unordered array
# Missing multiple consecutive numbers
assert solution.findMissingElements([1, 2, 6]) == [3, 4, 5] # large gap
# Missing one number in middle
assert solution.findMissingElements([2, 4, 3, 6]) == [5] # single missing value
# Already complete range
assert solution.findMissingElements([3, 4, 5, 6, 7]) == [] # no gaps
# Non-consecutive sparse case
assert solution.findMissingElements([1, 10]) == [2, 3, 4, 5, 6, 7, 8, 9] # many missing
| Test | Why |
|---|---|
[1,4,2,5] |
Validates a single missing number |
[7,8,6,9] |
Ensures empty result when range is complete |
[5,1] |
Tests only endpoints present |
[1,2] |
Smallest legal input size |
[1,100] |
Tests maximum possible range |
[10,7,8,5] |
Confirms unsorted input works correctly |
[1,2,6] |
Validates multiple consecutive missing numbers |
[2,4,3,6] |
Checks single missing value in the middle |
[3,4,5,6,7] |
Verifies already complete range |
[1,10] |
Tests sparse input with many missing values |
Edge Cases
No Missing Numbers
An important edge case occurs when every number between the minimum and maximum already exists.
For example:
[6,7,8,9]
A buggy implementation might mistakenly add values or mishandle an empty result. Our implementation correctly returns an empty list because every membership check succeeds and nothing is appended.
Only Endpoints Present
Another tricky case is when the array contains only the smallest and largest values.
For example:
[1,5]
Everything between them is missing. A careless implementation might fail to include all intermediate numbers or incorrectly assume neighboring values exist. Since our algorithm scans the entire range from minimum to maximum, every absent number is correctly collected.
Unsorted Input
The array is not guaranteed to be sorted.
For example:
[10,7,8,5]
A naive approach that assumes the first and last elements define the range would fail here. Our solution avoids this issue by explicitly computing min(nums) and max(nums), ensuring the correct boundaries regardless of input order.
Large Gaps Between Numbers
Inputs may contain large gaps.
For example:
[1,100]
A weak implementation might become inefficient by repeatedly scanning the array to check existence. Using a hash set guarantees fast lookups even when many values are missing.