LeetCode 448 - Find All Numbers Disappeared in an Array
The problem asks us to find all the integers in the range [1, n] that are missing from an input array nums of length n.
Difficulty: 🟢 Easy
Topics: Array, Hash Table
Solution
Problem Understanding
The problem asks us to find all the integers in the range [1, n] that are missing from an input array nums of length n. Each element in nums is guaranteed to be an integer between 1 and n inclusive, but some numbers in this range may not appear in the array, and some numbers may appear multiple times. The output is an array containing all integers from 1 to n that do not exist in nums.
For example, given nums = [4,3,2,7,8,2,3,1], the array has duplicates (2 and 3) and is missing 5 and 6. The correct output is [5,6].
The constraints indicate that n can be as large as 10^5, which means a naive quadratic solution will likely be too slow. Each element falls within the range [1, n], which is important because it allows us to use the input array itself for marking presence without extra space.
Important edge cases include arrays where all numbers are present (return empty), arrays where all numbers are missing except one, arrays of length 1, and arrays where duplicates cover the entire range except one number.
Approaches
The brute-force approach iterates over each number from 1 to n and checks if it exists in nums by scanning the array. This works correctly because it directly validates each possible number, but it is slow. For each of the n numbers, scanning an array of length n results in O(n²) time complexity, which is unacceptable for large inputs.
The optimal approach leverages the fact that the numbers in nums are all in the range [1, n]. This allows us to mark numbers as seen by modifying the input array itself. The key insight is to treat the array as a hash map: for each number num, we mark the element at index num - 1 as negative to indicate that num is present. After processing all numbers, the indices that remain positive correspond to the missing numbers. This approach uses O(1) extra space (ignoring the output array) and runs in O(n) time.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n²) | O(1) | Scan for each number from 1 to n in the array |
| Optimal | O(n) | O(1) | Mark visited numbers in-place by negating the corresponding index |
Algorithm Walkthrough
- Iterate through each number in the input array
nums. For each numbernum, compute the corresponding indexidx = abs(num) - 1. Theabsis necessary because we may have already marked this index as negative in a previous iteration. - Mark the number at index
idxas negative to indicate thatidx + 1exists in the array. If it is already negative, leave it as is. - After the first pass, iterate through the array again. For each index
iwherenums[i]is positive, it means that the numberi + 1did not appear in the original array. - Collect all such numbers into a result array and return it.
Why it works: By marking indices corresponding to the numbers seen, we maintain a direct mapping between indices and numbers. The negative marking is a safe in-place modification because we never need the original sign of a number for future computations, only its absolute value. This guarantees that every missing number is detected by its positive index.
Python Solution
from typing import List
class Solution:
def findDisappearedNumbers(self, nums: List[int]) -> List[int]:
# Step 1: Mark indices corresponding to numbers present
for num in nums:
idx = abs(num) - 1
if nums[idx] > 0:
nums[idx] = -nums[idx]
# Step 2: Collect indices that remain positive
result = []
for i, num in enumerate(nums):
if num > 0:
result.append(i + 1)
return result
The Python implementation follows the algorithm exactly. The first loop marks all indices corresponding to numbers seen by negating them. The second loop scans for positive values, which indicate missing numbers. Using abs(num) ensures that previously marked negative numbers do not interfere with index computation.
Go Solution
func findDisappearedNumbers(nums []int) []int {
n := len(nums)
// Step 1: Mark indices corresponding to numbers present
for _, num := range nums {
idx := abs(num) - 1
if nums[idx] > 0 {
nums[idx] = -nums[idx]
}
}
// Step 2: Collect indices that remain positive
var result []int
for i, num := range nums {
if num > 0 {
result = append(result, i+1)
}
}
return result
}
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
In Go, the main difference is the need for an explicit abs function. The logic is otherwise identical: mark indices in place and collect positive values. Go slices handle dynamic appending naturally.
Worked Examples
Example 1: nums = [4,3,2,7,8,2,3,1]
Step-by-step processing:
| Step | nums state | Action |
|---|---|---|
| Initial | [4,3,2,7,8,2,3,1] | - |
| Mark 4 | [4,3,2,-7,8,2,3,1] | nums[3] = -7 |
| Mark 3 | [4,3,-2,-7,8,2,3,1] | nums[2] = -2 |
| Mark 2 | [4,-3,-2,-7,8,2,3,1] | nums[1] = -3 |
| Mark 7 | [4,-3,-2,-7,8,2,-3,1] | nums[6] = -3 |
| Mark 8 | [4,-3,-2,-7,8,2,-3,-1] | nums[7] = -1 |
| Mark 2 | [4,-3,-2,-7,8,2,-3,-1] | already negative |
| Mark 3 | [4,-3,-2,-7,8,2,-3,-1] | already negative |
| Mark 1 | [-4,-3,-2,-7,8,2,-3,-1] | nums[0] = -4 |
Second pass collects indices with positive values: indices 4 and 5 correspond to numbers 5 and 6. Output: [5,6].
Example 2: nums = [1,1]
After marking: [-1,1]. Index 1 is positive, meaning number 2 is missing. Output: [2].
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each loop over nums is O(n), no nested iterations |
| Space | O(1) | In-place marking, output array does not count as extra space |
The algorithm is linear because it processes each element exactly twice, once for marking and once for collecting results. It does not use any additional data structures except the output array.
Test Cases
# provided examples
assert Solution().findDisappearedNumbers([4,3,2,7,8,2,3,1]) == [5,6] # multiple missing numbers
assert Solution().findDisappearedNumbers([1,1]) == [2] # only one missing number
# edge cases
assert Solution().findDisappearedNumbers([1]) == [] # single element present
assert Solution().findDisappearedNumbers([2]) == [1] # single element missing
assert Solution().findDisappearedNumbers([1,2,3,4,5]) == [] # all numbers present
assert Solution().findDisappearedNumbers([2,2,2,2,2]) == [1,3,4,5] # duplicates cover some numbers
assert Solution().findDisappearedNumbers([5,4,3,2,1]) == [] # all numbers present, unordered
assert Solution().findDisappearedNumbers([1,1,2,2]) == [3,4] # multiple duplicates
| Test | Why |
|---|---|
[4,3,2,7,8,2,3,1] |
Validates multiple missing numbers and duplicates |
[1,1] |
Validates single missing number with duplicate |
[1] |
Single element present, no missing numbers |
[2] |
Single element missing, minimal size |
[1,2,3,4,5] |
No missing numbers, all present |
[2,2,2,2,2] |
Duplicates covering some numbers, multiple missing numbers |
[5,4,3,2,1] |
All numbers present, unordered |
[1,1,2,2] |
Multiple duplicates, checks correct identification of missing numbers |
Edge Cases
One important edge case is an array of length 1. If the array contains