Find Missing Elements visualization

3731: Find Missing Elements

Difficulty: Easy
Topics: Array, Hash Table, Sorting
Companies: Google, Amazon, Microsoft
Pro Tip: The key insight is that we only need to check numbers between the minimum and maximum values in the array, since the smallest and largest elements are guaranteed to be present.

Problem Statement

Given an integer array nums of unique integers, find all missing integers in the range between the smallest and largest elements. The smallest and largest elements are guaranteed to be present in the array.

Example 1

Input: nums = [1,4,2,5]

Output: [3]

Explanation: Range is [1,5], only 3 is missing.

Example 2

Input: nums = [7,8,6,9]

Output: []

Explanation: Range is [6,9], all numbers are present.

Example 3

Input: nums = [5,1]

Output: [2,3,4]

Explanation: Range is [1,5], missing 2, 3, and 4.

Problem Link: View on LeetCode ↗

Key Insight

The solution relies on these crucial observations:

  1. The smallest and largest elements are always present in the array
  2. We only need to check numbers between min and max (inclusive)
  3. All integers in the array are unique
  4. The range is small (1 to 100), so even O(n²) approaches are efficient
Important: The constraint 2 ≤ nums.length ≤ 100 and 1 ≤ nums[i] ≤ 100 makes the problem suitable for various approaches.

Approach 1: Hash Set (Optimal)

Use a hash set to store all numbers for O(1) lookups, then iterate through the range to find missing numbers.

Algorithm Steps

  1. Find the minimum and maximum values in the array
  2. Store all numbers in a hash set for fast lookups
  3. Iterate from min to max (inclusive)
  4. Add numbers not found in the set to the result
  5. Return the result list

Complexity Analysis

Time Complexity Space Complexity
O(n) O(n)

Linear time for building set and checking range, linear space for the hash set.

Hash Set Approach
class Solution {
public:
    vector<int> findMissingElements(vector<int>& nums) {
        unordered_set<int> numSet(nums.begin(), nums.end());
        int minNum = *min_element(nums.begin(), nums.end());
        int maxNum = *max_element(nums.begin(), nums.end());
        
        vector<int> result;
        for (int i = minNum; i <= maxNum; i++) {
            if (numSet.find(i) == numSet.end()) {
                result.push_back(i);
            }
        }
        
        return result;
    }
};
import java.util.*;

class Solution {
    public List<Integer> findMissingElements(int[] nums) {
        Set<Integer> numSet = new HashSet<>();
        int minNum = Integer.MAX_VALUE;
        int maxNum = Integer.MIN_VALUE;
        
        for (int num : nums) {
            numSet.add(num);
            minNum = Math.min(minNum, num);
            maxNum = Math.max(maxNum, num);
        }
        
        List<Integer> result = new ArrayList<>();
        for (int i = minNum; i <= maxNum; i++) {
            if (!numSet.contains(i)) {
                result.add(i);
            }
        }
        
        return result;
    }
}
class Solution:
    def findMissingElements(self, nums: List[int]) -> List[int]:
        num_set = set(nums)
        min_num = min(nums)
        max_num = max(nums)
        
        result = []
        for i in range(min_num, max_num + 1):
            if i not in num_set:
                result.append(i)
                
        return result
Efficiency: This approach runs in O(n) time with O(n) space, making it optimal for the given constraints.

Approach 2: Sorting and Linear Scan

Sort the array and then scan through the range to find gaps where numbers are missing.

Algorithm Steps

  1. Sort the array in ascending order
  2. Find the minimum and maximum values (first and last elements after sorting)
  3. Use a pointer to track expected numbers in the range
  4. Iterate through sorted array and find missing numbers
  5. Return the result list

Complexity Analysis

Time Complexity Space Complexity
O(n log n) O(1)

Sorting dominates time complexity, constant space excluding output.

Sorting Approach
class Solution {
public:
    vector<int> findMissingElements(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int minNum = nums[0];
        int maxNum = nums.back();
        
        vector<int> result;
        int expected = minNum;
        
        for (int num : nums) {
            while (expected < num) {
                result.push_back(expected);
                expected++;
            }
            expected = num + 1;
        }
        
        return result;
    }
};
import java.util.*;

class Solution {
    public List<Integer> findMissingElements(int[] nums) {
        Arrays.sort(nums);
        int minNum = nums[0];
        int maxNum = nums[nums.length - 1];
        
        List<Integer> result = new ArrayList<>();
        int expected = minNum;
        
        for (int num : nums) {
            while (expected < num) {
                result.add(expected);
                expected++;
            }
            expected = num + 1;
        }
        
        return result;
    }
}
class Solution:
    def findMissingElements(self, nums: List[int]) -> List[int]:
        nums.sort()
        min_num = nums[0]
        max_num = nums[-1]
        
        result = []
        expected = min_num
        
        for num in nums:
            while expected < num:
                result.append(expected)
                expected += 1
            expected = num + 1
            
        return result
Advantage: This approach uses O(1) extra space (excluding output) and is easy to understand.

Approach 3: Boolean Array

Use a boolean array to mark presence of numbers in the range.

Algorithm Steps

  1. Find minimum and maximum values
  2. Create a boolean array of size (max - min + 1)
  3. Mark positions as true for numbers present in the array
  4. Collect indices that are false as missing numbers
  5. Return the result list

Complexity Analysis

Time Complexity Space Complexity
O(n) O(range)

Linear time, space proportional to the range between min and max.

Boolean Array Approach
class Solution {
public:
    vector<int> findMissingElements(vector<int>& nums) {
        int minNum = *min_element(nums.begin(), nums.end());
        int maxNum = *max_element(nums.begin(), nums.end());
        int rangeSize = maxNum - minNum + 1;
        
        vector<bool> present(rangeSize, false);
        for (int num : nums) {
            present[num - minNum] = true;
        }
        
        vector<int> result;
        for (int i = 0; i < rangeSize; i++) {
            if (!present[i]) {
                result.push_back(i + minNum);
            }
        }
        
        return result;
    }
};
class Solution {
    public List<Integer> findMissingElements(int[] nums) {
        int minNum = Integer.MAX_VALUE;
        int maxNum = Integer.MIN_VALUE;
        
        for (int num : nums) {
            minNum = Math.min(minNum, num);
            maxNum = Math.max(maxNum, num);
        }
        
        int rangeSize = maxNum - minNum + 1;
        boolean[] present = new boolean[rangeSize];
        
        for (int num : nums) {
            present[num - minNum] = true;
        }
        
        List<Integer> result = new ArrayList<>();
        for (int i = 0; i < rangeSize; i++) {
            if (!present[i]) {
                result.add(i + minNum);
            }
        }
        
        return result;
    }
}
class Solution:
    def findMissingElements(self, nums: List[int]) -> List[int]:
        min_num = min(nums)
        max_num = max(nums)
        range_size = max_num - min_num + 1
        
        present = [False] * range_size
        for num in nums:
            present[num - min_num] = True
        
        result = []
        for i in range(range_size):
            if not present[i]:
                result.append(i + min_num)
                
        return result
Efficiency: Very efficient for small ranges, which is perfect for this problem (max range size is 100).

Edge Cases and Testing

1. No Missing Elements

Input: [1,2,3,4,5] → Output: []
Input: [6,7,8,9] → Output: []

2. All Elements Missing Except Ends

Input: [1,5] → Output: [2,3,4]
Input: [10,15] → Output: [11,12,13,14]

3. Single Missing Element

Input: [1,2,4,5] → Output: [3]
Input: [7,8,9,11] → Output: [10]

4. Random Missing Elements

Input: [1,3,5,7,9] → Output: [2,4,6,8]
Input: [2,5,8,11] → Output: [3,4,6,7,9,10]

5. Minimum Array Size

Input: [1,2] → Output: []
Input: [1,3] → Output: [2]
Important: Always test with arrays that have no missing elements, all elements missing between ends, and various patterns of missing elements.

Detailed Explanation

The problem requires finding all integers that are missing between the smallest and largest elements of the array. The key insight is that we only need to consider numbers in the range [min, max] since the endpoints are guaranteed to be present.

1
4
2
3
5

For the input [1,4,2,5]:

The solution approaches work as follows:

  1. Hash Set: Store all numbers in a set for O(1) lookups, then check each number in the range
  2. Sorting: Sort the array and find gaps between consecutive elements
  3. Boolean Array: Use array indices to mark presence of numbers

All approaches are efficient given the constraints (n ≤ 100, values ≤ 100), but the hash set approach is generally preferred for its simplicity and optimal time complexity.

Why it works: By focusing only on the range between min and max, we avoid unnecessary checks and efficiently find missing elements.

FAQs

1. Why are the smallest and largest elements guaranteed to be present?

This is given in the problem statement: "The smallest and largest integers of the original range are still present in nums." This constraint simplifies the problem.

2. What is the time complexity of the hash set approach?

The hash set approach runs in O(n) time for building the set and O(range) time for checking numbers, giving overall O(n) time complexity.

3. What is the space complexity of the hash set approach?

The space complexity is O(n) for storing the hash set, which is efficient for the given constraints.

4. When would the boolean array approach be better?

The boolean array approach is better when the range between min and max is small, which is always true for this problem (max range = 100).

5. Can the array contain duplicate elements?

No, the problem states that the array consists of unique integers, so no duplicates exist.

6. What is the maximum possible range size?

Since 1 ≤ nums[i] ≤ 100, the maximum range size is 100 (from 1 to 100).

7. How does the sorting approach handle consecutive numbers?

In the sorting approach, when numbers are consecutive, the expected pointer moves smoothly without adding any missing numbers to the result.

8. What happens if the array is already sorted?

All approaches work correctly regardless of the initial order. The sorting approach would still sort it, but this is efficient for small n.

9. Can this problem be solved in O(1) space?

The sorting approach uses O(1) extra space (excluding output), but modifies the input array. If we cannot modify the input, we need O(n) space.

10. What is the most efficient approach for large ranges?

For large ranges, the hash set approach is most efficient as it doesn't depend on the range size for space, only on the number of elements.