2598. Smallest Missing Non-negative Integer After Operations
Problem Description
You have a 0-indexed integer array nums
and an integer value
. You can perform operations on this array where each operation allows you to either add or subtract value
from any element in nums
. You can perform any number of these operations.
For example, if nums = [1,2,3]
and value = 2
, you could subtract 2
from the first element to get nums = [-1,2,3]
.
The MEX (minimum excluded) of an array is defined as the smallest non-negative integer that is not present in the array. For instance:
- The MEX of
[-1,2,3]
is0
(since 0 is the smallest non-negative integer not in the array) - The MEX of
[1,0,3]
is2
(since 2 is the smallest non-negative integer not in the array)
Your task is to find the maximum possible MEX value you can achieve after applying the add/subtract operations any number of times.
The key insight is that when you add or subtract value
from any element, you're essentially changing which remainder class (modulo value
) that element belongs to. By counting how many elements fall into each remainder class, you can determine the maximum MEX achievable. Starting from 0
, you check if you can form each integer by using elements from the appropriate remainder class. The first integer you cannot form is your maximum MEX.
Intuition
The crucial observation is that when we add or subtract value
from any element, we're not changing its remainder when divided by value
. For example, if value = 3
and we have an element 5
, then 5 % 3 = 2
. If we add 3
to get 8
, then 8 % 3 = 2
. If we subtract 3
to get 2
, then 2 % 3 = 2
. The remainder stays the same!
This means any element with remainder r
(when divided by value
) can be transformed into any non-negative integer of the form r, r + value, r + 2*value, r + 3*value, ...
through repeated operations.
So the question becomes: given the remainders of all elements, what's the maximum MEX we can achieve?
To build the smallest non-negative integers 0, 1, 2, 3, ...
, we need:
- For
0
: we need an element with remainder0
- For
1
: we need an element with remainder1
- For
2
: we need an element with remainder2
- ...
- For
value
: we need another element with remainder0
(sincevalue % value = 0
) - For
value + 1
: we need another element with remainder1
- And so on...
We can see a pattern: to form integer i
, we need an element with remainder i % value
.
Therefore, we count how many elements have each possible remainder (from 0
to value-1
). Then we try to form integers 0, 1, 2, ...
in order. For each integer i
, we check if we have any remaining elements with remainder i % value
. If we do, we use one (decrement the count). If we don't, then i
is our maximum MEX since we can't form it.
Solution Approach
The solution implements the intuition using a counting approach with a hash table.
Step 1: Count remainders
cnt = Counter(x % value for x in nums)
We create a counter cnt
that stores how many elements have each possible remainder when divided by value
. This preprocessing step transforms our array into remainder counts.
Step 2: Find the maximum MEX
for i in range(len(nums) + 1):
if cnt[i % value] == 0:
return i
cnt[i % value] -= 1
We iterate through integers starting from 0
up to len(nums)
(the maximum possible MEX is at most the length of the array since we can only form at most len(nums)
distinct non-negative integers).
For each integer i
:
- We check the remainder
i % value
- If
cnt[i % value] == 0
, it means we don't have any elements left that can be transformed intoi
, soi
is our answer (the maximum MEX) - Otherwise, we use one element with remainder
i % value
to formi
, so we decrementcnt[i % value]
by 1
The algorithm guarantees we find the smallest non-negative integer that cannot be formed, which is exactly the maximum MEX we can achieve.
Time Complexity: O(n)
where n
is the length of the array, since we iterate through the array once to count remainders and at most n+1
times to find the MEX.
Space Complexity: O(min(n, value))
for storing the remainder counts in the hash table.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through an example with nums = [3, 0, 1, 5]
and value = 2
.
Step 1: Count remainders
First, we calculate the remainder of each element when divided by value = 2
:
3 % 2 = 1
0 % 2 = 0
1 % 2 = 1
5 % 2 = 1
Our remainder counts: cnt = {0: 1, 1: 3}
This tells us:
- We have 1 element with remainder 0 (the element
0
) - We have 3 elements with remainder 1 (the elements
3
,1
, and5
)
Step 2: Find the maximum MEX
Now we try to form integers 0, 1, 2, 3, ...
in sequence:
-
Forming 0: We need remainder
0 % 2 = 0
. We havecnt[0] = 1
, so we can form 0. Use one element with remainder 0:cnt[0] = 0
-
Forming 1: We need remainder
1 % 2 = 1
. We havecnt[1] = 3
, so we can form 1. Use one element with remainder 1:cnt[1] = 2
-
Forming 2: We need remainder
2 % 2 = 0
. We havecnt[0] = 0
(we used it for forming 0), so we cannot form 2.
Therefore, the maximum MEX is 2.
How the transformations work:
- The element
0
(remainder 0) stays as0
- The element
1
(remainder 1) stays as1
- The element
3
(remainder 1) could be transformed to1
by subtracting2
- The element
5
(remainder 1) could be transformed to1
or3
by subtracting4
or2
respectively
Since we can only form {0, 1}
but not 2
, the MEX of our final array is 2
.
Solution Implementation
1class Solution:
2 def findSmallestInteger(self, nums: List[int], value: int) -> int:
3 # Count frequency of each remainder when dividing by value
4 # This groups numbers by their modulo class
5 remainder_count = Counter(num % value for num in nums)
6
7 # Try each integer starting from 0 to find the smallest missing one
8 # We need at most len(nums) + 1 iterations since we can form at most len(nums) integers
9 for i in range(len(nums) + 1):
10 # Check if we can form integer i using available numbers
11 # Integer i requires a number with remainder (i % value)
12 if remainder_count[i % value] == 0:
13 # No number available with the required remainder
14 return i
15
16 # Use one number with the required remainder to form integer i
17 remainder_count[i % value] -= 1
18
1class Solution {
2 public int findSmallestInteger(int[] nums, int value) {
3 // Create an array to count the frequency of each remainder when divided by value
4 int[] remainderCount = new int[value];
5
6 // Count the frequency of each remainder (handling negative numbers properly)
7 for (int num : nums) {
8 // Calculate the positive remainder for negative numbers
9 // (num % value + value) % value ensures we get a positive remainder
10 int remainder = ((num % value) + value) % value;
11 remainderCount[remainder]++;
12 }
13
14 // Find the smallest non-negative integer that cannot be formed
15 for (int i = 0; ; i++) {
16 // Check if we can form the number i
17 // We need a number with remainder (i % value) to form i
18 int requiredRemainder = i % value;
19
20 // If no number with the required remainder is available, return i
21 if (remainderCount[requiredRemainder] == 0) {
22 return i;
23 }
24
25 // Use one number with this remainder to form i
26 remainderCount[requiredRemainder]--;
27 }
28 }
29}
30
1class Solution {
2public:
3 int findSmallestInteger(vector<int>& nums, int value) {
4 // Create an array to count occurrences of each remainder class modulo 'value'
5 int remainderCount[value];
6 memset(remainderCount, 0, sizeof(remainderCount));
7
8 // Count how many numbers fall into each remainder class
9 // Use ((x % value) + value) % value to handle negative numbers correctly
10 for (int num : nums) {
11 int remainder = ((num % value) + value) % value;
12 ++remainderCount[remainder];
13 }
14
15 // Find the smallest non-negative integer that cannot be formed
16 // by adding multiples of 'value' to elements in nums
17 for (int i = 0; ; ++i) {
18 int remainderClass = i % value;
19
20 // If no number in this remainder class is available, return i
21 if (remainderCount[remainderClass] == 0) {
22 return i;
23 }
24
25 // Use one number from this remainder class
26 --remainderCount[remainderClass];
27 }
28 }
29};
30
1/**
2 * Finds the smallest non-negative integer that cannot be formed by adding 'value' to any element in nums
3 * @param nums - Array of integers to process
4 * @param value - The increment value that can be added to elements
5 * @returns The smallest non-negative integer not achievable
6 */
7function findSmallestInteger(nums: number[], value: number): number {
8 // Create an array to count remainders when dividing by 'value'
9 // This tracks how many numbers in 'nums' have each possible remainder modulo 'value'
10 const remainderCount: number[] = new Array(value).fill(0);
11
12 // Count the frequency of each remainder class
13 // Using ((x % value) + value) % value to handle negative numbers correctly
14 for (const num of nums) {
15 const remainder: number = ((num % value) + value) % value;
16 remainderCount[remainder]++;
17 }
18
19 // Iterate through non-negative integers starting from 0
20 // Check if each integer can be formed by adding multiples of 'value' to elements in 'nums'
21 for (let candidate = 0; ; candidate++) {
22 // Get the remainder class this candidate belongs to
23 const remainderClass: number = candidate % value;
24
25 // If no element in nums can form this candidate (count is 0), return it
26 if (remainderCount[remainderClass] === 0) {
27 return candidate;
28 }
29
30 // Decrement the count as we've "used" one element from this remainder class
31 remainderCount[remainderClass]--;
32 }
33}
34
Time and Space Complexity
The time complexity is O(n)
and the space complexity is O(value)
, where n
is the length of the array nums
.
Time Complexity Analysis:
- Creating the Counter from
x % value for x in nums
takesO(n)
time as it iterates through all elements innums
- The for loop runs at most
n + 1
iterations (from0
tolen(nums)
) - Inside each iteration, checking
cnt[i % value]
and decrementing it are bothO(1)
operations - Therefore, the overall time complexity is
O(n) + O(n) = O(n)
Space Complexity Analysis:
- The Counter
cnt
stores the frequency of remainders when dividing byvalue
- Since we're using modulo operation (
x % value
), the possible keys in the Counter range from0
tovalue - 1
- In the worst case, the Counter can have at most
value
distinct keys - Therefore, the space complexity is
O(value)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall: Incorrect Handling of Negative Numbers' Remainders
The Problem: In Python (and many programming languages), the modulo operation for negative numbers doesn't always produce the expected mathematical remainder. For example:
- In Python:
-3 % 5 = 2
(correct mathematical behavior) - In Java/C++:
-3 % 5 = -3
(language-specific behavior)
While Python handles this correctly, developers coming from other languages might write incorrect remainder calculations, or when porting this solution to other languages, they might encounter unexpected results.
Example of the Issue:
# In some languages, you might incorrectly try to "fix" negative remainders: remainder = num % value if remainder < 0: remainder += value # This is unnecessary in Python and would be wrong!
The Solution:
In Python, num % value
already produces the correct non-negative remainder for negative numbers. However, if implementing in other languages or wanting to be explicit:
class Solution:
def findSmallestInteger(self, nums: List[int], value: int) -> int:
# Explicitly ensure non-negative remainders (defensive programming)
remainder_count = Counter()
for num in nums:
# Mathematical modulo: always returns non-negative remainder
remainder = ((num % value) + value) % value
remainder_count[remainder] += 1
for i in range(len(nums) + 1):
if remainder_count[i % value] == 0:
return i
remainder_count[i % value] -= 1
This defensive approach using ((num % value) + value) % value
guarantees a non-negative remainder in any programming language, making the solution more portable and explicit about the intended behavior.
Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!