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.
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:
- Find the minimum element in
O(1)
time - 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
- It removes the smallest element (
- 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 arrayO(k log n)
for performingk
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 EvaluatorExample 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)
takesO(n)
time to build the min-heap from the array- The loop runs
k
iterations, and eachheapreplace
operation takesO(log n)
time since it removes the minimum element and inserts a new element while maintaining the heap property reduce
with multiplication takesO(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 usesO(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
Which data structure is used in a depth first search?
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
https assets algo monster cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue is a data structure
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!