41. First Missing Positive - Detailed Explanation
Problem Statement
Description:
Given an unsorted array of integers nums
, find the smallest positive integer (greater than 0) that is missing from the array. In other words, we need to identify the first positive number that does not appear in nums
. The solution must run in O(n) time using O(1) extra space.
Examples:
-
Example 1:
Input:nums = [1, 2, 0]
Output:3
Explanation: The array contains1
and2
, so the smallest positive missing is3
(since1
and2
are present, the next positive is3
). -
Example 2:
Input:nums = [3, 4, -1, 1]
Output:2
Explanation: The numbers1
and3
and4
are in the array, but2
is missing. So2
is the smallest positive that isn't present. -
Example 3:
Input:nums = [7, 8, 9, 11, 12]
Output:1
Explanation: The array has no1
, so the smallest positive missing is1
itself. -
Example 4:
Input:nums = [1, 1, 2, 2]
Output:3
Explanation: The array has1
and2
(even though they appear multiple times), so the next smallest positive missing is3
.
Constraints:
-
1 <= nums.length <= 10^5
(the array has at least one element and at most 100,000 elements). -
-2^31 <= nums[i] <= 2^31 - 1
(elements can be negative, zero, or positive, within the range of 32-bit signed integers). -
Important: The solution is expected to run in linear time O(n) and use constant extra space O(1) (meaning we cannot use additional arrays or data structures that scale with n, aside from a few variables).
The goal is to efficiently find the smallest missing positive integer given these conditions.
Hints
Before diving into solutions, consider these hints to guide your thinking:
-
Hint 1: What is the maximum possible value for the smallest missing positive? Think about the size of the array. If the array has length
n
, the smallest missing positive must be in the range1
ton+1
. (Why? If the array contains all positives from 1 up to n, then the answer would be n+1. It can’t be larger than n+1.) -
Hint 2: Negative numbers and zeros do not affect the smallest positive missing value. You can effectively ignore any number
<= 0
or greater thann
when searching for the answer, since the answer will be between1
andn+1
by the hint above. Consider focusing only on the relevant range of values. -
Hint 3: Is there a way to use the array indices to mark which numbers are present? For example, if you could mark an index corresponding to a number when that number appears, you could then find the first index that wasn’t marked. This might allow you to solve the problem in O(n) time without extra space by using the array itself as a tracking tool.
-
Hint 4: If extra space were allowed, a simple approach would be to use a hash set or a boolean array to record which positives appear in
nums
. How might that work, and can you emulate a similar idea in-place (within the original array)?
Keep these hints in mind as you consider different approaches to the problem.
Multiple Approaches
There are multiple ways to solve this problem, ranging from straightforward but inefficient to more clever and optimal. We will discuss four approaches: a brute-force method, a method using extra space (hash set or boolean array), a sorting-based solution, and the optimal in-place solution (using a cyclic sort / index placement technique).
Approach 1: Brute Force (Naive Linear Search)
Idea:
The most straightforward approach is to check each positive integer starting from 1
and see if it’s in the array. Increase the number one by one until you find one that is missing.
How it works:
-
Start with
current = 1
(the smallest positive number). -
Scan through the entire array to see if
current
is present. -
If it is found, increment
current
by 1 and repeat the scan for the next number. -
If it is not found, then
current
is the answer (the first missing positive). -
If you finish scanning for all numbers up to some point without finding a missing one, the answer would eventually be
n+1
(where n is the array length).
Example:
Consider nums = [3, 4, -1, 1]
.
- Start with
current = 1
. Scan the array: is1
innums
? Yes (at the last position). - Since 1 is present, set
current = 2
. Scan again: is2
innums
? We check all elements (3, 4, -1, 1) and do not find2
. - We found a missing number:
2
. We can return2
as soon as we realize it's not in the array.
For this example, the brute force checked 1
(found it) and 2
(missing, so answer). In the worst case, if the array contains 1, 2, 3, ..., n
, the brute force will check all those and then n+1
as well.
Complexity:
- Time: O(n^2) in the worst case. For each candidate number, we may scan the entire array (which is O(n) per check), and in the worst case we might check up to n+1 numbers, resulting in roughly O(n * n). For example, if
nums = [1, 2, 3, ..., n]
, we will scan to find1
, then2
, ..., up ton+1
. This is extremely slow for large n (if n = 100,000, n^2 is 10^10 operations!). - Space: O(1) extra space (we only use a few variables regardless of array size).
Note: This approach is very simple to understand but not feasible for large inputs due to its quadratic time. We mention it as a baseline. We can improve upon it by eliminating the need to repeatedly scan the array for each number.
Approach 2: Using Extra Space (Hash Set or Boolean Array)
Idea:
We can reduce the time dramatically by using an auxiliary data structure to check for presence of numbers in O(1) time (average). The idea is to record which positive numbers appear in nums
, then iterate through the positives in order to find the first missing one. A hash set or a boolean array can be used for this purpose.
How it works:
- Build a set (or boolean list) of positives: Go through
nums
and add each number to a set if it’s positive. (Alternatively, create a boolean arraypresent
of size n initialized to false, and markpresent[x-1] = true
for each positivex
innums
that lies in1..n
range.) - Scan for the missing number: Starting from
1
up ton+1
, check if the number is in the set (or ifpresent[i-1]
is true). The first number not found in the set is the answer. - Return that missing number.
Using a set avoids scanning the whole array for each number – membership checks in a set are on average O(1) time, so this approach runs in O(n) time overall.
Example (using a set):
For nums = [3, 4, -1, 1]
:
- Build set of positives: we iterate once and add
{3, 4, 1}
(we ignore-1
because it’s not positive). - Now check from
1
upward:- Is
1
in the set? Yes. - Is
2
in the set? No – as soon as we see2
is not in the set, we have our answer. Output2
.
(We don’t need to check3
or4
because we already found the missing number2
.)
- Is
Example (using a boolean array):
For nums = [3, 4, -1, 1]
(length n=4):
- Create
present = [False, False, False, False]
corresponding to [1,2,3,4]. - Mark the entries: ignore negatives/zero. Mark index
0
true (because1
is present), index2
true (3
is present), index3
true (4
is present). Nowpresent = [True, False, True, True]
(meaning 1 is present, 2 is not, 3 is present, 4 is present). - Scan
present
: index 0 corresponds to1
(True, found), index 1 corresponds to2
(False, missing!). So answer = 2.
Complexity:
- Time: O(n) on average. We do one pass to build the set/marker, and another pass (at most n+1 checks) to find the missing number. This is linear time.
- Space: O(n) in the worst case for the extra data structure. In the worst case, we might store all n elements in the set or use a boolean array of length n.
Note: This approach is efficient in time and simpler to implement. However, it does not meet the problem’s requirement of using constant extra space because the extra set/array grows with n. In interview or contest settings where O(n) space is acceptable, this is a fine solution. But given the constraints (which ask for O(1) auxiliary space), we should explore how to achieve the result without using additional data structures.
Approach 3: Sorting-Based Approach
Idea:
Another strategy is to sort the array, which puts all the numbers in order. Then the smallest missing positive can be found by scanning through the sorted array and looking for the first gap in the sequence of positive integers.
How it works:
- Sort the array. This takes O(n log n) time typically.
- Initialize a variable
expected = 1
(the smallest positive we hope to find next). - Traverse through the sorted array:
- Skip any values that are <= 0, since we only care about positive numbers.
- For each positive number
x
in the sorted array:- If
x == expected
, that means we found the number we were expecting, so incrementexpected
(now we will look for the next number). - If
x < expected
, it’s either a duplicate or just a smaller number we’ve already accounted for; just skip it. - If
x > expected
, we've encountered a gap. This meansexpected
is not in the array (there’s a jump), soexpected
is the answer. We can break out.
- If
- If we finish iterating through the array without finding any gap, then all numbers 1 through whatever were present. The answer will be
expected
(which at that point would ben+1
if 1..n were all found).
Example:
Consider nums = [3, 4, -1, 1]
.
- Sorted, this becomes
[-1, 1, 3, 4]
. - Set
expected = 1
. - Iterate sorted list:
-1
is negative, ignore it (continue).1
equalsexpected
(1). Found 1, so setexpected = 2
(now we expect 2 next).- Next element
3
. Now3 > expected (2)
. This means2
was missing in the sequence (we expected 2 but found 3). We can stop here – the answer is2
.
- We output
2
.
Another example: nums = [1, 2, 0]
.
- Sorted:
[0, 1, 2]
. expected = 1
.- Iterate:
0
(ignore, not positive),1
matches expected -> now expected = 2,2
matches expected -> now expected = 3. Reached end of array. - All numbers 1 and 2 were found, so the smallest missing is
expected = 3
. Output3
.
Another example: nums = [7, 8, 9]
.
- Sorted:
[7, 8, 9]
. expected = 1
. The first element7
is already> expected
(7 > 1), which means1
is missing right away. We can output1
.
Complexity:
- Time: O(n log n) due to sorting. The subsequent scan is O(n). For moderate n this is fine, but sorting 100k elements is more expensive than a linear scan and might be borderline if n is very large or time limits are strict.
- Space: O(1) auxiliary (if we sort in place) or O(n) (if the sorting algorithm uses additional space). Typically, languages use in-place or O(n) space sorts; we won't dwell on this detail, since the main drawback is the time complexity.
Note: Sorting makes the logic to find the missing positive straightforward. However, because the problem explicitly asks for O(n) time, this may not be optimal for very large arrays. We can do better by using the array itself to achieve linear time and constant space.
Approach 4: Optimal In-Place (Cyclic Sort / Index Placement)
Idea:
This is the intended optimal solution, running in O(n) time with O(1) extra space. The idea is often called cyclic sort or the index placement technique. We leverage the fact that we know the answer is in the range 1
to n+1
, and we try to place each number in its "correct" index position in the array (much like how we would if the array were to be sorted, but we do it without a full sort). After rearranging, the first position that is out of place will indicate the answer.
Key insight: If the array length is n
, then the smallest missing positive must be between 1
and n+1
. That means any number larger than n
or non-positive can never be the answer and can be treated as irrelevant (or thought of as "missing placeholders"). Only the values 1
through n
matter for finding the first missing positive. Our goal is to organize the array so that if a number x
(where 1 <= x <= n
) is present, it ends up at index x-1
. For example, if 5
is in the array and is within the range [1, n], we want 5
to reside at index 4
(since indices are 0-based). After we do this positioning, if the array contains all numbers 1 through k, they will occupy indices 0 through k-1 correctly. The first index that doesn’t have the correct value indicates the missing number.
How it works (algorithm):
-
Partition the array by relevance: We can first ignore or skip any number that is out of our range [1, n]. In practice, we’ll handle this in the swapping logic, but conceptually, you can imagine we’re only interested in values 1..n.
-
Place numbers in their correct indices: Iterate through the array (index
i
from 0 to n-1):-
While the number at
nums[i]
is in the range1..n
and is not already in the correct position (i.e.,nums[i] != nums[nums[i] - 1]
), swap it with the number at its target positionnums[i] - 1
. -
Keep swapping until the current
nums[i]
is either out of range or already in the correct spot. This may involve multiple swaps for a single indexi
(each swap places one number in its correct position). We do not incrementi
during the inner swaps – thewhile
loop takes care of placing the correct value ati
before moving on. -
If the number at
i
is out of range or if it's already correct, move to the next index.
-
-
Find the first index with incorrect value: After the above pass, iterate through the array from index 0 upward. The first index
j
such thatnums[j] != j+1
will indicate thatj+1
is the smallest missing positive. If you reach the end and every index was correct (meaning the array contains 1 through n), then the answer isn+1
(the next positive after the largest index).
This approach modifies the array in-place by swapping elements around, but it ensures that by the end of the process, if a number k
(1 <= k <= n) is present in the array, it will be positioned at index k-1
. Any index that doesn’t satisfy this property reveals a missing number.
Example Walkthrough (basic idea):
For nums = [3, 4, -1, 1]
(we will do a detailed step-by-step in the next section, but here's an overview):
- Length n = 4, so we care about numbers 1 through 4.
- Start with index 0: value is 3, which is in [1,4] but not at index 2 where it should be (because 3 should be at index 2). Swap it with whatever is at index 2. After swap, 3 goes to index 2, and the value from index 2 comes to index 0. We keep swapping at index 0 until an out-of-range or correct value appears at index 0.
- Continue this for each index. We end up rearranging the array into something like
[1, -1, 3, 4]
. - Now check indices: index0 has 1 (good), index1 should have 2 but has -1 (wrong), so the answer is 2.
Why this works: By swapping each in-range number into its correct slot, we effectively bucket sort the array's useful portion (the numbers 1..n). If a number is missing, its slot will never get the correct number. For instance, if 2
is missing, index 1 will never contain 2
after this procedure – it might still contain some other number or an out-of-range placeholder. Thus, when we scan afterward, the first place where nums[i] != i+1
is exactly the missing number.
Complexity:
- Time: O(n). Each number will be swapped at most once to its correct position (each element goes to its correct place and then the index moves on). In total, the number of swaps is at most n, and each index is visited a constant number of times (the while loop moves numbers until the current index is correct, and each number swap puts at least one number in its final place). Hence the overall time is linear.
- Space: O(1) auxiliary. The rearrangement is done in-place, and we only use a few variables for indexing and swapping.
We will provide a more detailed step-by-step walkthrough of this optimal approach in a later section, with a visual example to illustrate how the array evolves and how the answer is identified.
Code Implementations
Below are implementations of the optimal O(n) in-place solution (Approach 4) in both Python and Java.
Python Implementation
Explanation: The code uses a while-loop to continually swap nums[i]
into its correct position (index nums[i]-1
) as long as nums[i]
is a value between 1 and n and not already positioned correctly. After this first phase, it scans for the first index j
where nums[j] != j+1
and returns j+1
as the answer. We copy the list with nums[:]
when printing results to avoid modifying the original test list for subsequent tests.
Java Implementation
In the Java code, we similarly perform swaps to position numbers correctly. The main
method runs through several test cases (including the examples and some edge cases) to demonstrate the output.
Complexity Analysis
Let’s summarize the time and space complexity for each approach discussed:
-
Brute Force Approach: Time O(n^2), Space O(1).
Explanation: For each candidate number (up to n+1 in worst case), we scan the array of length n to check if it’s present. This nested operation makes it quadratic time. No extra space besides a few counters is used. -
Using Extra Space (HashSet/Boolean Array): Time O(n) on average, Space O(n).
Explanation: Building the set or boolean array is O(n), and checking from 1 to n+1 is O(n) in worst case. Using a hash set assumes average O(1) membership checks. The space complexity is linear because of the additional data structure that can hold up to n elements or n booleans. -
Sorting-Based Approach: Time O(n log n), Space O(1) (or O(n) depending on sort implementation).
Explanation: Sorting dominates the time complexity. After sorting, scanning for the missing positive is O(n). Space depends on the sorting algorithm (in-place sorts use constant extra space, but some languages use O(n) space for sorting). We count it as O(1) extra for simplicity here, not including the output or input storage. -
Optimal In-Place Approach (Cyclic Sort Index Placement): Time O(n), Space O(1) .
Explanation: We perform at most n swaps and at most 2n total loop iterations (n for the initial placement loop and n for the checking loop), which is linear. Space is constant because we reorganize the array in place and only use a few index/temp variables. This meets the strict requirements of the problem.
Comparison: The in-place approach is clearly the best in terms of Big-O for large input sizes, as it scales linearly. The sorting approach is a close second in terms of simplicity but is slower for very large n. The hash set approach is linear time but fails the space constraint (which might be fine in practical scenarios with ample memory). The brute force approach is mostly theoretical here to illustrate the basic idea – it would be too slow on large inputs.
Step-by-Step Walkthrough of the Optimal Solution
Let's walk through the Optimal In-Place (Approach 4) with a detailed example to see how it works in action. We’ll use a tricky example that covers various cases (out-of-range values, negatives, needing multiple swaps):
Example: nums = [3, 4, -1, 1]
(from Example 2)
Initial array (with indices for clarity):
Index: 0 1 2 3
Value: [3, 4, -1, 1]
n = 4
We expect the answer to be 2
for this input. Let’s see how the algorithm finds that.
Phase 1: Place numbers in correct positions via swapping.
We iterate i
from 0 to 3.
-
i = 0:
- Current value
nums[0] = 3
. - 3 is in the range 1..4 and not at its correct position. The correct index for value 3 is
3 - 1 = 2
. - The value at index 2 is
-1
. We swap the values at index 0 and index 2.
After swap:
Index: 0 1 2 3 Value: [-1, 4, 3, 1]
Now, at index 0 we have
-1
. We will re-check index 0 in the while-loop:nums[0] = -1
, which is not in 1..4 (it's out of range), so we stop swapping at i=0.- (Index 0 now holds an "irrelevant" value
-1
that we can ignore from now on. Essentially, index 0 didn't get a correct positive placed because 1 was missing from the array, which we'll discover later.)
- Current value
-
i = 1:
nums[1] = 4
.- 4 is in range 1..4 and not at its correct position (correct index for 4 is
4-1 = 3
). - Value at index 3 is
1
. Swap index 1 and index 3.
After swap:
Index: 0 1 2 3 Value: [-1, 1, 3, 4]
Now,
nums[1]
has become1
(after the swap). We continue the while-loop for index 1:nums[1] = 1
, which is in range and should be at index1-1 = 0
.- Value at index 0 is
-1
. Swap index 1 with index 0.
After swap:
Index: 0 1 2 3 Value: [ 1, -1, 3, 4]
Now,
nums[1]
is-1
.-1
is out of range, so we stop swapping at i=1.- What happened here is that we successfully placed
1
into its correct slot (index 0). Index 1 now has-1
(an out-of-range placeholder) after placing 1 where it belongs.
-
i = 2:
nums[2] = 3
.- 3 is in range and should be at index
2
(since 3’s correct index is 2). Is it already there? Yes,nums[2]
is 3 and index 2 corresponds to value 3 (which means this is correct,nums[2] == 3
and we expect3
at index 2). - So
nums[2]
is already in the right place. No swap needed. Move on.
-
i = 3:
nums[3] = 4
.- 4 is in range and should be at index
3
. It is indeed at index 3 already (nums[3] == 4
). - No swap needed.
After the first phase, the array has been rearranged to:
Index: 0 1 2 3
Value: [ 1, -1, 3, 4]
Let's interpret this state:
-
At index 0, we have 1. Ideally, we want
nums[0] == 1
for the array to be "complete" from 1... That is correct. -
At index 1, we would want
nums[1] == 2
if 2 were present. But we have-1
(which is just a leftover non-positive number). This indicates that2
was never placed in the array – likely because 2 is not in the array at all. -
Index 2 has 3 (correct, since we want
nums[2] == 3
). -
Index 3 has 4 (correct, since we want
nums[3] == 4
).
We can already see that index 1 is out-of-place (it doesn’t have 2). The algorithm will detect that in the next phase.
Phase 2: Find the first index with a mismatch.
Now we do a linear scan to find the first index j
such that nums[j] != j+1
:
-
j = 0
: Checknums[0]
against1
. Herenums[0] = 1
, and1 == 1
(expected value), so index 0 is fine. -
j = 1
: Checknums[1]
against2
. We findnums[1] = -1
, and this is not equal to2
(the value that should be here if everything from 1 up to 2 were present). This is the first discrepancy.
This means2
is missing. We immediately identify that the smallest missing positive isj+1 = 1+1 = 2
. We can return2
.
We don’t need to check further indices once we found the first mismatch. The answer for this example is indeed 2
.
This walkthrough shows how the array is rearranged so that each index tries to hold the correct number (index + 1
). Any index that fails to have the correct number indicates the missing positive. In our example, index 1 failed to have 2
, hence 2 was missing.
Additional thoughts on this approach with different scenarios:
-
If the array were something like
[1,2,3]
(already containing 1 through n), the rearrangement phase would leave it unchanged (each index already has the correct value). The second phase would check index0 (has 1, good), index1 (has 2, good), index2 (has 3, good) and then finish the loop and returnn+1
which is 4. This correctly handles the case where the array contains all numbers from 1 to n. -
If the array starts without
1
(e.g.,[2,3,4]
for n=3), then:- During placement: 2 and 3 will get placed to indices 1 and 2, and 1 is never placed (since it doesn’t exist in the array, index 0 will remain with an out-of-range number like 4 or something after swaps).
- In the second phase, at index 0 we’ll find
nums[0] != 1
and return 1 immediately. This handles the case where1
is missing (the answer is 1, the smallest positive).
-
Duplicate values don’t break the algorithm: if a number is duplicated, the swap condition
nums[i] != nums[nums[i]-1]
will fail once the duplicate is in place, preventing an infinite loop. For example,[1,1]
: at i=0, we won’t swap becausenums[0] == nums[nums[0]-1]
(both are 1). The algorithm will then move on, and in phase 2, index 1 will be mismatched (expect 2, found 1), yielding 2 as answer.
The in-place approach is robust and covers all these scenarios systematically.
Common Mistakes & Edge Cases
When implementing this solution (or similar ones), it’s easy to run into some pitfalls. Here are common mistakes and important edge cases to watch out for:
Common Pitfalls:
-
Infinite Loop in Swap: If the swap logic is not implemented carefully, you could end up in an infinite loop. For instance, forgetting to check the condition
nums[i] != nums[nums[i] - 1]
before swapping can cause a case where you keep swapping the same two equal values back and forth. The condition ensures that if a number is already in its correct place (or a duplicate is already placed at its target), we don't swap endlessly. -
Using incorrect index calculations: Remember that the target index for a value
x
isx-1
(because of 0-based indexing). Off-by-one errors can lead to either index out-of-bound errors or incorrect placement. Double-check indexing math. -
Skipping elements during swap: When a swap occurs, do not increment the index
i
immediately. The swapped in value atnums[i]
needs to be processed in the next iteration of the while-loop at the same index. Some implementations mistakenly increasei
inside the loop, which can skip processing a value that was just swapped in. In our approach, we use awhile
loop specifically to handle this – we only movei
forward in the outerfor
loop when the current position is settled. -
Not accounting for values out of range: It’s important to only swap values that are in the range 1..n. If you don't check
nums[i] <= n
andnums[i] >= 1
, you might try to place an out-of-range value into an index that doesn't exist, causing errors. We explicitly ignore values<= 0
or> n
by not entering the swap loop for them (treating them as already "in place" in a sense that they’re in a useless spot). -
Forgetting the final case (n+1): After rearranging and checking the array, if all positions 1 through n are filled correctly, the answer is n+1. A common mistake is to not handle this and thus miss the scenario where the array contains all smaller positives. For example, if
nums = [1,2,3]
, a faulty solution might not return 4. In our implementations, we returnn+1
if no missing index is found in the second loop.
Important Edge Cases:
-
Array of length 1: e.g.,
[1]
or[2]
.- If the array is
[1]
, the answer should be 2 (since 1 is present, next positive missing is 2). - If the array is
[2]
(or any value not 1), the answer should be 1 (because 1 is missing). Our algorithm handles this: it will not swap anything, and in the check phase, index 0 expects 1 but finds 2, so it returns 1.
- If the array is
-
All non-positive or large values: e.g.,
[-1, -2]
or[100, 200]
.- These contain no positive numbers in the range 1..n at all. The smallest missing positive is 1 in such cases. The in-place algorithm will essentially ignore all values (since none are in 1..n) and then immediately find index 0 is wrong (expecting 1 but it's something else or unchanged), returning 1.
-
Already sorted or consecutive sequence: e.g.,
[1,2,3,4]
.- Here, the answer should be 5. The algorithm will place everything (they are already placed), then after checking all indices correct, return n+1 which is 5.
-
Missing number in the middle: e.g.,
[1,2,4,5]
(missing 3).- The algorithm will place 1 and 2 correctly, place 4 and 5 (5 is out of range if n=4, so it will be ignored or treated as placeholder), and then find index 2 has 4 instead of 3, returning 3.
-
Duplicates and unsorted mix: e.g.,
[2, 2, 1, 1]
.- Duplicates should not confuse the algorithm. In this example, after placement, we should end up with
[1, 2, ...]
in the first two indices (the exact content of later indices isn't important), and the answer will be 3. The algorithm's swap logic naturally handles duplicates by not swapping a number into a position that already holds the same number.
- Duplicates should not confuse the algorithm. In this example, after placement, we should end up with
-
Largest values present: e.g., an array containing values like
[1, 2, ..., n]
where n is the length.- We covered this: the answer is n+1, which the algorithm returns after verifying all positions.
By considering these edge cases, we ensure the solution is robust. The provided code examples handle all these cases correctly.
Alternative Variations
The problem of finding the first missing positive is a classic application of using array indexing tricks to achieve linear time without extra space. Here are some variations and related insights:
-
Alternate Optimal Solution (Marking Technique): Instead of swapping elements in place, another common approach for this problem is to mark the presence of numbers by using the array indices as flags. For example, one can:
- First, replace any number that is out of range (<=0 or >n) with a dummy value like
n+1
. This way, all numbers in the array are now positive and within a manageable range. - Then, for each number
x
in the array (now all are >0), if1 <= x <= n
, mark its presence by making the value at indexx-1
negative (multiply by -1 as a flag, for instance). - Finally, the first index that has a positive value indicates the missing number (because its index was never marked), otherwise if all indices 0..n-1 are marked (negative), answer is n+1.
This approach achieves the same O(n) time and O(1) space goal without explicit swapping. It’s a bit trickier to implement correctly (handling the sign flips carefully and not double-flipping a value). The swapping method and the marking method are two sides of the same coin – both effectively use the array itself to track presence/absence of numbers.
- First, replace any number that is out of range (<=0 or >n) with a dummy value like
-
Cyclic Sort Technique: The approach we used is an example of the cyclic sort technique, which is broadly useful in problems dealing with finding missing or duplicate numbers in a range. The idea is to repeatedly swap misplaced elements until everything that can be placed correctly is placed. This technique can be applied to similar problems (see related problems below).
-
Different ranges or k-th missing: A variation of missing positive problems is finding the k-th missing positive number, or the smallest missing in a different range. For instance, there's a problem where given a sorted array of positive numbers, you need to find the k-th missing positive (which can be solved via binary search or two-pointer since the array is sorted). That is a different scenario because the input is sorted and you allow skipping larger gaps. It uses a different approach (counting gaps), but it’s related in the sense of reasoning about "missing" values.
-
Multiple missing numbers: Sometimes you might be asked to find all missing numbers in a range (not just the first). One can extend these ideas to gather all missing positives (for example, LeetCode’s "Find All Numbers Disappeared in an Array" asks for all missing numbers from 1..n). The marking technique can be easily extended to collect all such indices. The cyclic sort can also be extended: after placement, scan and collect all indices where
nums[i] != i+1
to get all missing numbers. -
Using similar index placement for duplicates: A related class of problems uses a similar concept to find duplicates. For example, to find a duplicate number in an array, you can swap or mark as you traverse. One known problem is to find the one duplicate in an array of length n+1 where numbers are in [1..n]; this can be done by placing numbers and seeing which index ends up with the wrong number (or via Floyd’s cycle detection). The index marking (negation) trick is often used to find duplicates by seeing if an index was visited twice (you’d find a value already negative).
In summary, the technique used in "First Missing Positive" is widely applicable. The essence is: when you have a range of integers [1..n] and you want to find something missing or duplicate, consider using the array’s indices as a proxy for those values. Either place each value at its index or mark the index when a value is seen. This way, you can avoid extra space. Many array problems of this nature can be tackled with this mindset.
Related Problems for Further Practice
To strengthen your understanding, you can practice these related LeetCode problems that involve similar concepts:
-
LeetCode 268. Missing Number: Given an array of size n containing numbers from 0 to n, find the one number missing from 0..n. (This is simpler than First Missing Positive because here the range is 0..n and only one number is missing with no extra negatives or large values. Can be solved with sum/XOR or similar index ideas.)
-
LeetCode 448. Find All Numbers Disappeared in an Array: Given an array of length n with numbers in 1..n (some appear, some missing, some may repeat), find all the numbers in [1..n] that are not in the array. (This can be solved by using the same index marking technique by negating values at indices. It’s like finding all missing positives instead of just the first one.)
-
LeetCode 442. Find All Duplicates in an Array: Given an array of length n with numbers in 1..n, some appear twice, find all duplicates. (Can also be solved with a similar marking strategy: when you encounter a number, flip the sign at its index; if you find it’s already negative, that number is a duplicate. Or using cyclic sort and checking mismatches.)
-
LeetCode 645. Set Mismatch: An array of length n contains numbers 1..n but one number is missing and another is duplicated. Find the duplicate and the missing. (This is a combination of missing and duplicate finding; you can use index placement or marking to detect both anomalies in one pass.)
-
LeetCode 41. First Missing Positive: (The problem we just solved – listed here for completeness, as many lists include the problem itself as a reference.)
-
LeetCode 1539. Kth Missing Positive Number: Given a sorted array of positive integers and an integer k, find the k-th positive integer that is missing from the array. (This is a different twist: the input is sorted and you have to count missing numbers until you reach the k-th one. It uses a different approach, typically a binary search or linear scan counting gaps, since the array is sorted. It’s good to see the contrast in approach when the input conditions change.)
GET YOUR FREE
Coding Questions Catalog
