This is a good example of using the input array itself as bookkeeping space.
The numbers are constrained to 1..n, which means each value can point to one meaningful index.
Problem 1: Find All Numbers Disappeared in an Array
Problem description:
Given an array nums of length n where each value is in the range [1, n], some numbers appear twice and others are missing. Return all numbers in the range [1, n] that do not appear in the array.
What we are solving actually:
The array already gives us a built-in mapping from value to index: value v corresponds to index v - 1. Instead of using a separate set or boolean array, we can mark seen values directly inside nums.
What we are doing actually:
- For each value
v, map it to indexv - 1. - Mark that target index as negative to mean “this value exists.”
- Use
Math.absbecause earlier markings may already have changed signs. - In a second pass, any index still positive corresponds to a missing value.
class Solution {
public List<Integer> findDisappearedNumbers(int[] nums) {
List<Integer> result = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
int idx = Math.abs(nums[i]) - 1;
if (nums[idx] > 0) {
nums[idx] = -nums[idx]; // Negative means the value (idx + 1) has been seen.
}
}
for (int i = 0; i < nums.length; i++) {
if (nums[i] > 0) {
result.add(i + 1); // Positive means this value was never marked as seen.
}
}
return result;
}
}
Debug steps:
- print the array after each marking step to watch sign changes
- test
[4,3,2,7,8,2,3,1],[1,1], and[1,2,3,4] - verify the invariant that index
ibeing negative means valuei + 1appeared at least once
In-Place Marking Idea
Because values are guaranteed to be in [1, n]:
- value
1maps to index0 - value
2maps to index1 - …
- value
nmaps to indexn - 1
So every value can “mark its own slot.” Negative sign becomes a cheap visited flag.
That is the whole trick.
Dry Run
Input: [4,3,2,7,8,2,3,1]
Marking phase:
- value
4-> mark index3 - value
3-> mark index2 - value
2-> mark index1 - value
7-> mark index6 - value
8-> mark index7 - value
2-> index1already negative, so do nothing - value
3-> index2already negative - value
1-> mark index0
After marking, the array has positive values at indices:
45
So the missing numbers are:
56
Why Math.abs Is Required
During marking, entries become negative. But we still need the original magnitude to figure out which index to mark next.
Example:
- if
nums[i]is-3 - it still represents value
3
That is why we compute:
int idx = Math.abs(nums[i]) - 1
Without Math.abs, the index calculation becomes wrong.
Input Mutation Note
This algorithm mutates the input array.
That is the trade-off that buys O(1) extra space.
If mutation is not allowed:
- use a boolean array
- or use a
HashSet
Those alternatives are simpler conceptually, but they use O(n) extra space.
Common Mistakes
- forgetting
Math.abswhen reading the current value - adding
iinstead ofi + 1to the answer - expecting the original input signs to remain unchanged
- using extra memory when the problem specifically asks for constant extra space
Complexity
- Time:
O(n) - Space:
O(1)extra, excluding the output list
Key Takeaways
- value-to-index mapping is the whole invariant behind this solution
- sign flipping is just a compact “seen” marker
- if index
istays positive, then valuei + 1is missing
Comments