2439. Minimize Maximum of Array
Problem Description
You have an array nums with n non-negative integers (0-indexed).
You can perform the following operation any number of times:
- Pick an index
iwhere1 <= i < nandnums[i] > 0 - Decrease
nums[i]by 1 - Increase
nums[i-1]by 1
This operation essentially moves one unit from position i to position i-1 (shifting value leftward).
Your goal is to minimize the maximum value in the array after performing any number of these operations. Return this minimum possible maximum value.
For example, if you have nums = [3, 7, 1, 6], you can perform operations to redistribute values leftward to minimize the largest element in the resulting array.
The key insight is that you can only move values leftward (from higher indices to lower indices), never rightward. This means each element can potentially receive contributions from elements to its right, but can only give to the element immediately to its left.
The solution uses binary search to find the minimum possible maximum value. For each candidate maximum value mx, it checks whether it's achievable by simulating the process backwards - starting from the rightmost element and calculating how much excess needs to be pushed left if we want to keep all elements at most mx. If the first element (which cannot push anything further left) ends up exceeding mx after receiving all the excess, then mx is not achievable.
Intuition
The first observation is that we're trying to minimize the maximum value, which suggests binary search might be useful. Why? Because if we can achieve a maximum value of mx, then we can also achieve any value greater than mx. This monotonic property makes binary search applicable.
The second key insight is understanding what the operations actually do. Since we can only move values from right to left (from index i to i-1), think of it like water flowing downhill from right to left. Each position can receive "water" from positions to its right and pass it further left.
Now, how do we check if a certain maximum value mx is achievable? We need to verify that we can redistribute the array values such that no element exceeds mx.
Working backwards from right to left makes sense here. For each element nums[i], if it exceeds mx, we need to push the excess (nums[i] - mx) to the left. This excess accumulates as we move left. When we reach nums[0], it has nowhere to push its excess - it must absorb everything. If nums[0] plus all the accumulated excess is still <= mx, then mx is achievable.
Why does this greedy approach work? Because we're forced to handle all excess - we can't leave any element above mx. By processing from right to left and pushing all excess leftward, we're essentially finding the minimum burden on nums[0]. If even this minimum burden makes nums[0] exceed mx, then mx is impossible to achieve.
The binary search range is from 0 to max(nums) because:
- The minimum possible maximum is at least
0(or could be the average for a tighter bound) - The maximum possible maximum is the current maximum in the array (we can't increase it)
Learn more about Greedy, Binary Search, Dynamic Programming and Prefix Sum patterns.
Solution Implementation
1class Solution: 2 def minimizeArrayValue(self, nums: List[int]) -> int: 3 """ 4 Find the minimum possible maximum value in the array after operations. 5 Operations allow moving value from nums[i] to nums[i-1] for i > 0. 6 7 Args: 8 nums: List of integers to process 9 10 Returns: 11 The minimum possible maximum value after all operations 12 """ 13 14 def feasible(target_max: int) -> bool: 15 """ 16 Check if we can make all elements <= target_max by moving values left. 17 18 Args: 19 target_max: The maximum value we're trying to achieve 20 21 Returns: 22 True if achievable, False otherwise 23 """ 24 excess = 0 25 26 # Process array from right to left (excluding the first element) 27 for current_value in nums[:0:-1]: 28 excess = max(0, excess + current_value - target_max) 29 30 # Check if first element can absorb all excess 31 return nums[0] + excess <= target_max 32 33 # Binary search using the first_true_index template 34 left, right = 0, max(nums) 35 first_true_index = -1 36 37 while left <= right: 38 mid = (left + right) // 2 39 40 if feasible(mid): 41 first_true_index = mid 42 right = mid - 1 43 else: 44 left = mid + 1 45 46 return first_true_index 471class Solution { 2 private int[] nums; 3 4 /** 5 * Minimizes the maximum value in the array after performing operations. 6 * Uses binary search with first_true_index template. 7 * 8 * @param nums The input array to minimize 9 * @return The minimum possible maximum value after operations 10 */ 11 public int minimizeArrayValue(int[] nums) { 12 this.nums = nums; 13 14 // Binary search using first_true_index template 15 int left = 0; 16 int right = findMaxElement(nums); 17 int firstTrueIndex = -1; 18 19 while (left <= right) { 20 int mid = left + (right - left) / 2; 21 22 if (feasible(mid)) { 23 firstTrueIndex = mid; 24 right = mid - 1; 25 } else { 26 left = mid + 1; 27 } 28 } 29 30 return firstTrueIndex; 31 } 32 33 /** 34 * Checks if it's possible to make all elements <= targetMax. 35 * 36 * @param targetMax The target maximum value to check 37 * @return true if achievable, false otherwise 38 */ 39 private boolean feasible(int targetMax) { 40 long excess = 0; 41 42 // Process array from right to left (excluding first element) 43 for (int i = nums.length - 1; i > 0; i--) { 44 excess = Math.max(0, excess + nums[i] - targetMax); 45 } 46 47 // Check if first element can absorb all excess 48 return nums[0] + excess <= targetMax; 49 } 50 51 /** 52 * Finds the maximum element in the array. 53 */ 54 private int findMaxElement(int[] nums) { 55 int maxValue = nums[0]; 56 for (int element : nums) { 57 maxValue = Math.max(maxValue, element); 58 } 59 return maxValue; 60 } 61} 621class Solution { 2public: 3 int minimizeArrayValue(vector<int>& nums) { 4 // Lambda function to check feasibility 5 auto feasible = [&](int targetMax) -> bool { 6 long long excess = 0; 7 8 // Iterate from the last element to the second element 9 for (int i = nums.size() - 1; i > 0; --i) { 10 excess = max(0LL, excess + nums[i] - targetMax); 11 } 12 13 // Check if first element can absorb all excess 14 return nums[0] + excess <= targetMax; 15 }; 16 17 // Binary search using first_true_index template 18 int left = 0; 19 int right = *max_element(nums.begin(), nums.end()); 20 int firstTrueIndex = -1; 21 22 while (left <= right) { 23 int mid = left + (right - left) / 2; 24 25 if (feasible(mid)) { 26 firstTrueIndex = mid; 27 right = mid - 1; 28 } else { 29 left = mid + 1; 30 } 31 } 32 33 return firstTrueIndex; 34 } 35}; 361function minimizeArrayValue(nums: number[]): number { 2 // Feasibility function 3 const feasible = (targetMax: number): boolean => { 4 let excess: number = 0; 5 6 // Iterate from right to left (excluding first element) 7 for (let i = nums.length - 1; i > 0; i--) { 8 excess = Math.max(0, excess + nums[i] - targetMax); 9 } 10 11 // Check if first element can absorb all excess 12 return nums[0] + excess <= targetMax; 13 }; 14 15 // Binary search using first_true_index template 16 let left: number = 0; 17 let right: number = Math.max(...nums); 18 let firstTrueIndex: number = -1; 19 20 while (left <= right) { 21 const mid: number = Math.floor((left + right) / 2); 22 23 if (feasible(mid)) { 24 firstTrueIndex = mid; 25 right = mid - 1; 26 } else { 27 left = mid + 1; 28 } 29 } 30 31 return firstTrueIndex; 32} 33Solution Approach
The solution uses binary search to find the minimum possible maximum value. The key insight is that this problem has a monotonic property: if we can achieve a maximum value of mx, we can also achieve any value greater than mx. This creates a feasibility pattern of [false, false, ..., true, true, ...] as we increase the target maximum.
Binary Search Template: We use the standard binary search template with first_true_index to find the minimum achievable maximum:
left, right = 0, max(nums) first_true_index = -1 while left <= right: mid = (left + right) // 2 if feasible(mid): first_true_index = mid right = mid - 1 else: left = mid + 1 return first_true_index
Feasible Function: The feasible(target_max) function determines if we can redistribute values such that no element exceeds target_max:
- Initialize
excess = 0to track accumulated values that must be pushed left - Iterate through the array from right to left (excluding the first element)
- For each element:
excess = max(0, excess + current_value - target_max) - Return
Trueifnums[0] + excess <= target_max
Why the Template Works Here:
- When
feasible(mid)returnsTrue, we recordmidas a candidate answer and search for smaller values (right = mid - 1) - When
feasible(mid)returnsFalse, we need larger values (left = mid + 1) - The
first_true_indextracks the minimum achievable maximum
Why the Greedy Check Works:
- Processing right to left pushes excess values leftward (the only direction allowed)
- Each position can reduce its value to at most
target_max, with excess going left - The first element must absorb all accumulated excess since it cannot push further left
- If
nums[0] + excess > target_max, the target is impossible
Time Complexity: O(n × log(max(nums))) where n is the array length Space Complexity: O(1) - only using a few variables
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 the solution with nums = [3, 7, 1, 6].
Step 1: Binary Search Setup
left = 0,right = max(nums) = 7,first_true_index = -1- We'll search for the minimum possible maximum value using the template
Step 2: First Iteration (left=0, right=7)
mid = (0 + 7) // 2 = 3- feasible(3):
- Start with
excess = 0 - Process
nums[3] = 6:excess = max(0, 0 + 6 - 3) = 3 - Process
nums[2] = 1:excess = max(0, 3 + 1 - 3) = 1 - Process
nums[1] = 7:excess = max(0, 1 + 7 - 3) = 5 - Check:
nums[0] + excess = 3 + 5 = 8 > 3→ False
- Start with
- Since
feasible(3) = False:left = mid + 1 = 4
Step 3: Second Iteration (left=4, right=7)
mid = (4 + 7) // 2 = 5- feasible(5):
- Start with
excess = 0 - Process
nums[3] = 6:excess = max(0, 0 + 6 - 5) = 1 - Process
nums[2] = 1:excess = max(0, 1 + 1 - 5) = 0 - Process
nums[1] = 7:excess = max(0, 0 + 7 - 5) = 2 - Check:
nums[0] + excess = 3 + 2 = 5 ≤ 5→ True
- Start with
- Since
feasible(5) = True:first_true_index = 5,right = mid - 1 = 4
Step 4: Third Iteration (left=4, right=4)
mid = (4 + 4) // 2 = 4- feasible(4):
- Start with
excess = 0 - Process
nums[3] = 6:excess = max(0, 0 + 6 - 4) = 2 - Process
nums[2] = 1:excess = max(0, 2 + 1 - 4) = 0 - Process
nums[1] = 7:excess = max(0, 0 + 7 - 4) = 3 - Check:
nums[0] + excess = 3 + 3 = 6 > 4→ False
- Start with
- Since
feasible(4) = False:left = mid + 1 = 5
Step 5: Termination
left = 5 > right = 4, loop exits- Return
first_true_index = 5
The final redistributed array would be [5, 5, 3, 4] after operations:
- Move 2 from index 3 to 2:
[3, 7, 3, 4] - Move 2 from index 1 to 0:
[5, 5, 3, 4]
Time and Space Complexity
Time Complexity: O(n × log M)
The algorithm uses binary search on the answer space from 0 to max(nums) (which is M). The binary search performs O(log M) iterations. In each iteration, the check function is called, which iterates through the array from the second-to-last element to the first element (excluding index 0), taking O(n) time where n is the length of the array. Therefore, the overall time complexity is O(n × log M).
Space Complexity: O(1)
The algorithm only uses a constant amount of extra space. The variables left, right, mid, and d in the check function all use constant space. The iteration through the array in the check function doesn't create any additional data structures proportional to the input size. Therefore, the space complexity is O(1).
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Using Wrong Binary Search Template Variant
The most important pitfall is using the wrong binary search template. This problem requires the first_true_index template with while left <= right and right = mid - 1:
Wrong Template:
while left < right: mid = (left + right) // 2 if feasible(mid): right = mid # WRONG: should be mid - 1 else: left = mid + 1 return left # WRONG: should return first_true_index
Correct Template:
left, right = 0, max(nums) first_true_index = -1 while left <= right: mid = (left + right) // 2 if feasible(mid): first_true_index = mid right = mid - 1 else: left = mid + 1 return first_true_index
2. Incorrect Iteration Direction in Feasible Function
A common mistake is iterating from left to right instead of right to left when checking feasibility:
Wrong Approach:
def feasible(mx): deficit = 0 for i in range(len(nums)): # LEFT to RIGHT - WRONG! if nums[i] + deficit > mx: deficit = nums[i] + deficit - mx return True # No way to validate this
Correct Approach:
def feasible(mx): excess = 0 for x in nums[:0:-1]: # RIGHT to LEFT - CORRECT! excess = max(0, excess + x - mx) return nums[0] + excess <= mx
3. Forgetting to Handle the First Element Separately
The first element (index 0) is special - it cannot pass values to the left:
Wrong:
def feasible(mx): excess = 0 for x in reversed(nums): # Includes nums[0] in the loop excess = max(0, excess + x - mx) return excess == 0 # Wrong validation
Correct:
def feasible(mx): excess = 0 for x in nums[:0:-1]: # Excludes nums[0] excess = max(0, excess + x - mx) return nums[0] + excess <= mx # Separate check for nums[0]
4. Integer Overflow with Large Values
When working with large numbers, accumulated excess can overflow. While Python handles big integers automatically, in other languages use appropriate data types (long long in C++, long in Java).
5. Misunderstanding the Operation Direction
Values can ONLY move from right to left (from nums[i] to nums[i-1]). If bidirectional movement were allowed, you could simply average all values.
Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?
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 Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Want a Structured Path to Master System Design Too? Don’t Miss This!