Facebook Pixel

2233. Maximum Product After K Increments

Problem Description

You have an array of non-negative integers called nums and a positive integer k. You can perform up to k operations on this array. In each operation, you can select any element from the array and increase it by 1.

Your goal is to maximize the product of all elements in the array after performing at most k operations. Since the resulting product can be extremely large, you need to return the answer modulo 10^9 + 7.

For example, if you have nums = [0, 4] and k = 5, you could add 1 to the first element five times, resulting in [5, 4], which gives a product of 20. However, a better strategy would be to distribute the increments more evenly: add 3 to the first element and 2 to the second element, resulting in [3, 6], which gives a product of 18. But the optimal approach is to add all 5 increments to the first element, giving [5, 4] with a product of 20.

The key insight is that to maximize the product, you should always increment the smallest element in the array. This is because increasing smaller numbers has a greater relative impact on the overall product than increasing larger numbers. The solution uses a min-heap to efficiently track and update the smallest element after each increment operation.

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

Intuition

To maximize a product, we need to think about how increments affect the overall result. Consider a simple example with two numbers: [2, 8]. If we add 1 to make it either [3, 8] (product = 24) or [2, 9] (product = 18), we see that increasing the smaller number gives a larger product.

This happens because of the multiplicative nature of the problem. When we increase a number x by 1, the product is multiplied by (x+1)/x. This ratio is larger for smaller values of x. For instance, going from 2 to 3 multiplies the product by 3/2 = 1.5, while going from 8 to 9 multiplies it by 9/8 = 1.125.

Therefore, the greedy strategy is to always increment the smallest element in the array. After each increment, the smallest element might change (it could still be the same element or a different one), so we need to find the new minimum efficiently.

This naturally leads us to use a min-heap data structure. A min-heap allows us to:

  1. Find the minimum element in O(1) time
  2. Remove the minimum and insert a new element in O(log n) time

The algorithm becomes straightforward: repeat k times the process of taking the smallest element, incrementing it by 1, and putting it back. This ensures that at each step, we're making the choice that maximizes the product increase, ultimately leading to the maximum possible product after all operations.

Learn more about Greedy and Heap (Priority Queue) patterns.

Solution Approach

The implementation uses a greedy algorithm with a min-heap (priority queue) to efficiently track and update the smallest element in the array.

Step 1: Initialize the Min-Heap
We convert the input array nums into a min-heap using Python's heapify() function. This operation arranges the elements so that the smallest element is always at index 0, and it takes O(n) time where n is the length of the array.

Step 2: Perform k Operations
We iterate exactly k times, and in each iteration:

  • Use heapreplace(nums, nums[0] + 1) which combines two operations:
    • It removes the smallest element (nums[0])
    • It inserts the incremented value (nums[0] + 1) back into the heap
  • This combined operation maintains the heap property and runs in O(log n) time

The heapreplace() function is more efficient than doing heappop() followed by heappush() as it performs both operations in a single step.

Step 3: Calculate the Product
After all k operations are complete, we calculate the product of all elements in the modified array. The solution uses Python's reduce() function with a lambda expression:

  • reduce(lambda x, y: x * y % mod, nums) multiplies all elements together
  • The modulo operation % mod is applied at each multiplication step to prevent integer overflow
  • mod = 10**9 + 7 is the specified modulo value

Time Complexity: O(n + k log n) where n is the length of the array

  • O(n) for heapifying the initial array
  • O(k log n) for performing k heap operations

Space Complexity: O(1) as we modify the array in-place (the heap uses the same array without additional space)

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 a concrete example with nums = [2, 5, 3] and k = 4.

Initial Setup:

  • Array: [2, 5, 3]
  • After heapifying: [2, 5, 3] (2 is at the root of the min-heap)
  • Initial product would be: 2 × 5 × 3 = 30

Operation 1:

  • Smallest element: 2
  • Increment it: 2 + 1 = 3
  • Array after heapreplace: [3, 5, 3] (heap reorders to maintain min-heap property)
  • Current product would be: 3 × 5 × 3 = 45

Operation 2:

  • Smallest element: 3 (could be either one, heap picks the root)
  • Increment it: 3 + 1 = 4
  • Array after heapreplace: [3, 5, 4] (heap maintains 3 as minimum)
  • Current product would be: 3 × 5 × 4 = 60

Operation 3:

  • Smallest element: 3
  • Increment it: 3 + 1 = 4
  • Array after heapreplace: [4, 5, 4] (both 4s are smaller than 5)
  • Current product would be: 4 × 5 × 4 = 80

Operation 4:

  • Smallest element: 4 (one of the two 4s)
  • Increment it: 4 + 1 = 5
  • Array after heapreplace: [4, 5, 5] (4 remains the minimum)
  • Final product: 4 × 5 × 5 = 100

Result: The maximum product is 100.

Notice how we always increment the smallest value. Initially we focused on bringing 2 up to 3, then continued with the smallest elements. This greedy approach ensures each increment provides the maximum relative increase to the product. If we had distributed increments differently (like adding all 4 to the 5 to make it 9), we'd get 2 × 9 × 3 = 54, which is much smaller than our result of 100.

Solution Implementation

1from heapq import heapify, heapreplace
2from functools import reduce
3from typing import List
4
5
6class Solution:
7    def maximumProduct(self, nums: List[int], k: int) -> int:
8        # Convert the list into a min-heap to efficiently access the smallest element
9        heapify(nums)
10      
11        # Perform k increment operations
12        # Each time, increment the smallest element by 1
13        for _ in range(k):
14            # Replace the smallest element with its value + 1
15            # heapreplace is more efficient than heappop followed by heappush
16            heapreplace(nums, nums[0] + 1)
17      
18        # Define the modulo value for the result
19        MOD = 10**9 + 7
20      
21        # Calculate the product of all elements in the array with modulo
22        # Use reduce to multiply all elements together while applying modulo
23        return reduce(lambda x, y: (x * y) % MOD, nums)
24
1class Solution {
2    public int maximumProduct(int[] nums, int k) {
3        // Create a min-heap to always access the smallest element efficiently
4        PriorityQueue<Integer> minHeap = new PriorityQueue<>();
5      
6        // Add all numbers from the array to the min-heap
7        for (int num : nums) {
8            minHeap.offer(num);
9        }
10      
11        // Perform k increment operations
12        // Strategy: Always increment the smallest number to maximize the final product
13        while (k-- > 0) {
14            // Remove the smallest element, increment it by 1, and add it back
15            int smallest = minHeap.poll();
16            minHeap.offer(smallest + 1);
17        }
18      
19        // Define the modulo value to prevent integer overflow
20        final int MOD = (int) 1e9 + 7;
21      
22        // Calculate the product of all elements in the heap
23        long product = 1;
24        for (int num : minHeap) {
25            product = (product * num) % MOD;
26        }
27      
28        // Return the final product as an integer
29        return (int) product;
30    }
31}
32
1class Solution {
2public:
3    int maximumProduct(vector<int>& nums, int k) {
4        // Use a min-heap to efficiently access and update the smallest element
5        priority_queue<int, vector<int>, greater<int>> minHeap;
6      
7        // Add all numbers to the min-heap
8        for (int num : nums) {
9            minHeap.push(num);
10        }
11      
12        // Perform k increment operations
13        // Each time, increment the smallest element by 1
14        while (k-- > 0) {
15            int minElement = minHeap.top();
16            minHeap.pop();
17            minHeap.push(minElement + 1);
18        }
19      
20        // Define modulo constant for preventing overflow
21        const int MOD = 1e9 + 7;
22      
23        // Calculate the product of all elements in the heap
24        long long product = 1;
25        while (!minHeap.empty()) {
26            product = (product * minHeap.top()) % MOD;
27            minHeap.pop();
28        }
29      
30        // Return the final product as an integer
31        return static_cast<int>(product);
32    }
33};
34
1/**
2 * Calculates the maximum product of array elements after performing k increment operations.
3 * Strategy: Use a min-heap to always increment the smallest element, which maximizes the final product.
4 * 
5 * @param nums - Array of integers to process
6 * @param k - Number of increment operations to perform
7 * @returns The maximum product modulo 10^9 + 7
8 */
9function maximumProduct(nums: number[], k: number): number {
10    // Initialize a min-heap to efficiently track and update the smallest element
11    const minHeap = new MinPriorityQueue();
12  
13    // Add all numbers to the min-heap
14    nums.forEach(num => minHeap.enqueue(num));
15  
16    // Perform k increment operations on the smallest elements
17    while (k-- > 0) {
18        // Extract the minimum element
19        const minElement = minHeap.dequeue().element;
20        // Increment it by 1 and add back to heap
21        minHeap.enqueue(minElement + 1);
22    }
23  
24    // Calculate the product of all elements
25    let product = 1;
26    const MOD = 10 ** 9 + 7;
27  
28    // Multiply all elements from the heap with modulo to prevent overflow
29    while (!minHeap.isEmpty()) {
30        const currentElement = minHeap.dequeue().element;
31        product = (product * currentElement) % MOD;
32    }
33  
34    return product;
35}
36

Time and Space Complexity

The time complexity is O(n + k × log n), where n is the length of the array nums and k is the number of operations.

  • heapify(nums) takes O(n) time to build the min-heap from the array
  • The loop runs k iterations, and each heapreplace operation takes O(log n) time since it removes the minimum element and inserts a new element while maintaining the heap property
  • reduce with multiplication takes O(n) time to compute the product of all elements

Therefore, the overall time complexity is O(n + k × log n), which simplifies to O(k × log n) when k × log n > n.

The space complexity is O(n), where n is the length of the array nums.

  • The heapify operation modifies the input array in-place, so no additional space is needed for the heap structure itself
  • The reduce function uses O(1) auxiliary space for the accumulator variable
  • The only significant space usage is the input array itself, which accounts for O(n) space

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Integer Overflow When Computing the Product

The most critical pitfall is computing the product without applying modulo at each multiplication step. Many developers might try to calculate the entire product first and then apply modulo at the end:

Incorrect approach:

# This can cause integer overflow even in Python for very large products
product = 1
for num in nums:
    product *= num
return product % MOD

Why it's problematic: While Python handles arbitrary-precision integers, multiplying large numbers can still lead to performance issues and memory problems. In other programming languages, this would cause integer overflow.

Correct approach:

# Apply modulo at each multiplication step
product = 1
for num in nums:
    product = (product * num) % MOD
return product

2. Not Handling the Zero Element Case Properly

When the array contains zeros, the greedy strategy of always incrementing the smallest element is especially important. Some might incorrectly try to distribute increments evenly or use a different strategy.

Example scenario:

  • nums = [0, 0, 0], k = 5
  • Incorrect thinking: "Distribute evenly" → [2, 2, 1] → product = 4
  • Correct approach: Always increment smallest → [2, 2, 1] → product = 4 (same result but for different reasons)
  • The key is when you have [0, 5, 5] vs [1, 5, 5] - incrementing the 0 gives much better improvement

3. Using heappop() + heappush() Instead of heapreplace()

Less efficient approach:

for _ in range(k):
    smallest = heappop(nums)
    heappush(nums, smallest + 1)

Why heapreplace() is better:

  • heapreplace() performs both operations in a single heap adjustment
  • It's more efficient as it only needs to traverse the heap once
  • Reduces the constant factor in the O(log n) operation

4. Modifying the Original Array Without Considering Side Effects

The solution modifies the input array in-place. If the original array needs to be preserved for other purposes, this could cause issues.

Solution if original array must be preserved:

def maximumProduct(self, nums: List[int], k: int) -> int:
    # Create a copy to avoid modifying the original
    nums_copy = nums.copy()
    heapify(nums_copy)
  
    for _ in range(k):
        heapreplace(nums_copy, nums_copy[0] + 1)
  
    MOD = 10**9 + 7
    return reduce(lambda x, y: (x * y) % MOD, nums_copy)

5. Not Understanding Why Greedy Works

Some might question why always incrementing the smallest element is optimal. The mathematical intuition is:

  • For numbers a ≤ b, the product (a+1)×b = ab + b is always greater than a×(b+1) = ab + a
  • Since b ≥ a, we get a larger increase by incrementing the smaller number
  • This principle holds throughout all k operations
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which data structure is used in a depth first search?


Recommended Readings

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

Load More