Facebook Pixel

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] is 0 (since 0 is the smallest non-negative integer not in the array)
  • The MEX of [1,0,3] is 2 (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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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 remainder 0
  • For 1: we need an element with remainder 1
  • For 2: we need an element with remainder 2
  • ...
  • For value: we need another element with remainder 0 (since value % value = 0)
  • For value + 1: we need another element with remainder 1
  • 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.

Learn more about Greedy and Math patterns.

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 into i, so i is our answer (the maximum MEX)
  • Otherwise, we use one element with remainder i % value to form i, so we decrement cnt[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 Evaluator

Example 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, and 5)

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 have cnt[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 have cnt[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 have cnt[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 as 0
  • The element 1 (remainder 1) stays as 1
  • The element 3 (remainder 1) could be transformed to 1 by subtracting 2
  • The element 5 (remainder 1) could be transformed to 1 or 3 by subtracting 4 or 2 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 takes O(n) time as it iterates through all elements in nums
  • The for loop runs at most n + 1 iterations (from 0 to len(nums))
  • Inside each iteration, checking cnt[i % value] and decrementing it are both O(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 by value
  • Since we're using modulo operation (x % value), the possible keys in the Counter range from 0 to value - 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the tree traversal order can be used to obtain elements in a binary search tree in sorted order?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More