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 i where 1 <= i < n and nums[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.

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

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 47
1class 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} 62
1class 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}; 36
1function 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} 33

Solution 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:

  1. Initialize excess = 0 to track accumulated values that must be pushed left
  2. Iterate through the array from right to left (excluding the first element)
  3. For each element: excess = max(0, excess + current_value - target_max)
  4. Return True if nums[0] + excess <= target_max

Why the Template Works Here:

  • When feasible(mid) returns True, we record mid as a candidate answer and search for smaller values (right = mid - 1)
  • When feasible(mid) returns False, we need larger values (left = mid + 1)
  • The first_true_index tracks 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 Evaluator

Example 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 > 3False
  • 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 ≤ 5True
  • 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 > 4False
  • 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.

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

Consider the classic dynamic programming of fibonacci numbers, what is the recurrence relation?


Recommended Readings

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

Load More