41. First Missing Positive - Explanation
Description
You are given an unsorted integer array nums. Return the smallest positive integer that is not present in nums.
You must implement an algorithm that runs in O(n) time and uses O(1) auxiliary space.
Example 1:
Input: nums = [-2,-1,0]
Output: 1Example 2:
Input: nums = [1,2,4]
Output: 3Example 3:
Input: nums = [1,2,4,5,6,3,1]
Output: 7Constraints:
1 <= nums.length <= 100,000-(2^31) <= nums[i] <= ((2^31)-1)
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- In-Place Array Modification - Using the input array itself as auxiliary storage to achieve O(1) space
- Cycle Sort - Placing elements at their "correct" indices through swapping
- Index Mapping - Understanding that value
vshould be at indexv-1for positive integers - Negative Marking Technique - Using the sign of array elements as boolean flags
1. Brute Force
Intuition
The simplest way to find the first missing positive is to just check each positive integer one by one.
We start with 1, scan the entire array looking for it, and if we find it, move on to 2, then 3, and so on.
The first number we can't find in the array is our answer.
This works because we're guaranteed the answer exists somewhere between 1 and n + 1 (where n is the array size). In the worst case, if the array contains exactly [1, 2, 3, ..., n], the answer would be n + 1.
Algorithm
- Start with
missing = 1. - Search the entire array for
missing. - If found, increment
missingand repeat. - If not found, return
missing.
Time & Space Complexity
- Time complexity:
- Space complexity:
2. Boolean Array
Intuition
Here's a key observation: if the array has n elements, the answer must be in the range [1, n + 1].
Why? Because even if the array contains n distinct positive integers, they can at most cover 1 through n, making n + 1 the answer.
So we only care about numbers from 1 to n. We can create a boolean array of size n where seen[i] tells us whether i + 1 exists in the input. Then we just find the first index that's still false.
Algorithm
- Create a boolean array
seenof sizen, initialized tofalse. - For each number in the input:
- If it's between
1andn, markseen[num - 1] = true.
- If it's between
- Scan
seenfrom index0ton - 1:- Return
i + 1for the firstfalseentry.
- Return
- If all entries are
true, returnn + 1.
Time & Space Complexity
- Time complexity:
- Space complexity:
3. Sorting
Intuition
If the array is sorted, finding the first missing positive becomes straightforward.
We walk through the sorted array while tracking the smallest positive integer we're looking for.
Whenever we see that number, we increment our target. The first target we don't find is the answer.
We skip negative numbers and zeros since they don't affect our search. Duplicates are also handled naturally since we only increment when we find an exact match.
Algorithm
- Sort the array.
- Initialize
missing = 1. - For each number in the sorted array:
- If
numis positive and equalsmissing, incrementmissing.
- If
- Return
missing.
Time & Space Complexity
- Time complexity:
- Space complexity: or depending on the sorting algorithm.
4. Negative Marking
Intuition
Can we achieve O(1) space without sorting? Yes, by using the input array itself as our hash map.
The idea is to use the sign of each element as a flag. If the value at index i is negative, it means i + 1 exists in the array. But there's a catch: the array might already contain negative numbers or zeros, which would interfere with our marking scheme.
So we first convert all non-positive numbers to 0. Then for each value v in the range [1, n], we mark the element at index v - 1 as negative. If it's already 0, we use a special marker -(n + 1) to indicate presence while keeping it distinguishable.
Finally, the first non-negative index tells us which number is missing.
Algorithm
- Replace all negative numbers with
0. - For each number
val(using absolute value to handle already-marked cells):- If
1 <= val <= n:- If
nums[val - 1] > 0, negate it. - If
nums[val - 1] == 0, set it to-(n + 1).
- If
- If
- Find the first index where
nums[i] >= 0and returni + 1. - If no such index exists, return
n + 1.
class Solution:
def firstMissingPositive(self, nums: list[int]) -> int:
for i in range(len(nums)):
if nums[i] < 0:
nums[i] = 0
for i in range(len(nums)):
val = abs(nums[i])
if 1 <= val <= len(nums):
if nums[val - 1] > 0:
nums[val - 1] *= -1
elif nums[val - 1] == 0:
nums[val - 1] = -1 * (len(nums) + 1)
for i in range(1, len(nums) + 1):
if nums[i - 1] >= 0:
return i
return len(nums) + 1Time & Space Complexity
- Time complexity:
- Space complexity: extra space.
5. Cycle Sort
Intuition
Another way to use the array as its own hash map is through cycle sort. The goal is to place each number at its "correct" index: value 1 at index 0, value 2 at index 1, and so on.
We iterate through the array, and for each element, if it's a valid positive number in range [1, n] and not already in its correct position, we swap it to where it belongs. We keep swapping until the current position holds the right value or an out-of-range number.
After this rearrangement, we scan the array. The first position where nums[i] != i + 1 gives us the missing number.
Algorithm
- Iterate through the array with index
i:- While
nums[i]is in range[1, n]andnums[i] != nums[nums[i] - 1]:- Swap
nums[i]withnums[nums[i] - 1].
- Swap
- While
- Scan the array:
- Return
i + 1for the first index wherenums[i] != i + 1.
- Return
- If all positions are correct, return
n + 1.
class Solution:
def firstMissingPositive(self, nums: list[int]) -> int:
n = len(nums)
i = 0
while i < n:
if nums[i] <= 0 or nums[i] > n:
i += 1
continue
index = nums[i] - 1
if nums[i] != nums[index]:
nums[i], nums[index] = nums[index], nums[i]
else:
i += 1
for i in range(n):
if nums[i] != i + 1:
return i + 1
return n + 1Time & Space Complexity
- Time complexity:
- Space complexity: extra space.
Common Pitfalls
Forgetting the Answer is Bounded by n+1
The first missing positive is always in the range [1, n+1]. Many solutions fail by not recognizing this constraint and trying to handle arbitrarily large numbers. If the array contains [1, 2, ..., n], the answer is n+1. This insight is crucial for achieving O(n) time and O(1) space.
Not Handling Negative Numbers and Zeros
The array can contain negative numbers, zeros, and duplicates. Solutions that use the array itself as a hash map (via negative marking or cycle sort) must first handle or ignore non-positive values. Forgetting to skip or neutralize these values leads to index-out-of-bounds errors or incorrect markings.
Infinite Loops in Cycle Sort
When using cycle sort, failing to check if nums[i] != nums[nums[i] - 1] before swapping causes infinite loops. If the current element equals the element at its target position (duplicate case), you must move forward instead of swapping. Always verify both the value is in range and the target position needs updating.
Corrupting Original Values Before Using Them
In negative marking approaches, reading nums[i] after it has been negated gives the wrong value. Always use abs(nums[i]) to retrieve the original value when the array has been modified. Similarly, marking a zero requires special handling since negating zero has no effect.
Sign in to join the discussion