Before attempting this problem, you should be comfortable with:
Binary Search - Searching on the answer space when the problem has monotonic properties
Prefix Sum - Computing cumulative sums to analyze constraints across array segments
Greedy Algorithms - Understanding how local optimal choices lead to global optimal solutions
1. Binary Search
Intuition
The operation allows us to move value from a position to its left neighbor. This means we can redistribute values leftward but never rightward. The key observation is: for any target maximum value m, we can achieve it if and only if the prefix sum up to each index i does not exceed m * (i + 1). This is because we can spread the total sum of the first i+1 elements evenly among those positions.
Since the answer lies between 0 and the maximum element, we can binary search for the smallest valid maximum. For each candidate, we check if the prefix sum constraint holds for all positions.
Algorithm
Binary search on the answer between 0 and max(nums).
For each candidate maximum mid, check validity:
Iterate through the array maintaining a running prefix_sum.
If at any index i, the prefix_sum exceeds mid * (i + 1), the candidate is too small.
If valid, try a smaller maximum. If invalid, try a larger one.
classSolution:defminimizeArrayValue(self, nums: List[int])->int:defisValid(maxVal):
prefix_sum =0for i inrange(len(nums)):
prefix_sum += nums[i]if prefix_sum > maxVal *(i +1):returnFalsereturnTrue
left, right =0,max(nums)while left < right:
mid = left +(right - left)//2if isValid(mid):
right = mid
else:
left = mid +1return left
publicclassSolution{publicintminimizeArrayValue(int[] nums){int left =0, right =0;for(int num : nums){
right =Math.max(right, num);}while(left < right){int mid = left +(right - left)/2;if(isValid(nums, mid)){
right = mid;}else{
left = mid +1;}}return left;}privatebooleanisValid(int[] nums,int maxVal){long prefixSum =0;for(int i =0; i < nums.length; i++){
prefixSum += nums[i];if(prefixSum >(long) maxVal *(i +1)){returnfalse;}}returntrue;}}
classSolution{public:intminimizeArrayValue(vector<int>& nums){int left =0, right =*max_element(nums.begin(), nums.end());while(left < right){int mid = left +(right - left)/2;if(isValid(nums, mid)){
right = mid;}else{
left = mid +1;}}return left;}private:boolisValid(vector<int>& nums,int maxVal){longlong prefixSum =0;for(int i =0;i< nums.size(); i++){
prefixSum += nums[i];if(prefixSum >(longlong)maxVal *(i +1)){returnfalse;}}returntrue;}};
classSolution{/**
* @param {number[]} nums
* @return {number}
*/minimizeArrayValue(nums){constisValid=(maxVal)=>{let prefixSum =0;for(let i =0; i < nums.length; i++){
prefixSum += nums[i];if(prefixSum > maxVal *(i +1)){returnfalse;}}returntrue;};let left =0,
right = Math.max(...nums);while(left < right){let mid = left + Math.floor((right - left)/2);if(isValid(mid)){
right = mid;}else{
left = mid +1;}}return left;}}
publicclassSolution{publicintMinimizeArrayValue(int[] nums){int left =0, right = nums.Max();while(left < right){int mid = left +(right - left)/2;if(IsValid(nums, mid)){
right = mid;}else{
left = mid +1;}}return left;}privateboolIsValid(int[] nums,int maxVal){long prefixSum =0;for(int i =0; i < nums.Length; i++){
prefixSum += nums[i];if(prefixSum >(long)maxVal *(i +1)){returnfalse;}}returntrue;}}
funcminimizeArrayValue(nums []int)int{
isValid :=func(maxVal int)bool{var prefixSum int64=0for i :=0; i <len(nums); i++{
prefixSum +=int64(nums[i])if prefixSum >int64(maxVal)*int64(i+1){returnfalse}}returntrue}
left, right :=0,0for_, num :=range nums {if num > right {
right = num
}}for left < right {
mid := left +(right-left)/2ifisValid(mid){
right = mid
}else{
left = mid +1}}return left
}
class Solution {funminimizeArrayValue(nums: IntArray): Int {funisValid(maxVal: Int): Boolean {var prefixSum: Long =0for(i in nums.indices){
prefixSum += nums[i]if(prefixSum > maxVal.toLong()*(i +1)){returnfalse}}returntrue}var left =0var right = nums.max()while(left < right){val mid = left +(right - left)/2if(isValid(mid)){
right = mid
}else{
left = mid +1}}return left
}}
Where n is the size of the array nums and m is the maximum value in the array.
2. Prefix Sum + Greedy
Intuition
Building on the prefix sum insight, we can solve this directly without binary search. At each position i, the minimum possible maximum considering elements 0 to i is the ceiling of prefixSum / (i + 1). This represents the best we can do by spreading the total sum evenly across those positions.
The overall answer is the maximum of these values across all prefixes. We cannot do better than this because each prefix constrains how low the maximum can go, and the tightest constraint determines the answer.
Algorithm
Initialize res and total with the first element (the first element cannot be reduced).
For each subsequent index i:
Add nums[i] to the running total.
Compute the ceiling of total / (i + 1).
Update res to be the maximum of itself and this ceiling.
classSolution:defminimizeArrayValue(self, nums: List[int])->int:
res = total = nums[0]for i inrange(1,len(nums)):
total += nums[i]
res =max(res, math.ceil(total /(i +1)))return res
publicclassSolution{publicintminimizeArrayValue(int[] nums){int res = nums[0];long total = nums[0];for(int i =1; i < nums.length; i++){
total += nums[i];
res =Math.max(res,(int)Math.ceil((double) total /(i +1)));}return res;}}
classSolution{public:intminimizeArrayValue(vector<int>& nums){int res = nums[0];longlong total = nums[0];for(int i =1; i < nums.size(); i++){
total += nums[i];
res =max(res,(int)ceil((double)total /(i +1)));}return res;}};
classSolution{/**
* @param {number[]} nums
* @return {number}
*/minimizeArrayValue(nums){let res = nums[0];let total = nums[0];for(let i =1; i < nums.length; i++){
total += nums[i];
res = Math.max(res, Math.ceil(total /(i +1)));}return res;}}
publicclassSolution{publicintMinimizeArrayValue(int[] nums){int res = nums[0];long total = nums[0];for(int i =1; i < nums.Length; i++){
total += nums[i];
res = Math.Max(res,(int)Math.Ceiling((double)total /(i +1)));}return res;}}
funcminimizeArrayValue(nums []int)int{
res := nums[0]
total :=int64(nums[0])for i :=1; i <len(nums); i++{
total +=int64(nums[i])
ceil :=int((total +int64(i))/int64(i+1))if ceil > res {
res = ceil
}}return res
}
class Solution {funminimizeArrayValue(nums: IntArray): Int {var res = nums[0]var total: Long = nums[0].toLong()for(i in1 until nums.size){
total += nums[i]
res =maxOf(res,((total + i)/(i +1)).toInt())}return res
}}
classSolution{funcminimizeArrayValue(_ nums:[Int])->Int{var res = nums[0]var total =Int64(nums[0])for i in1..<nums.count {
total +=Int64(nums[i])let ceil =Int((total +Int64(i))/Int64(i +1))
res =max(res, ceil)}return res
}}
Time & Space Complexity
Time complexity: O(n)
Space complexity: O(1) extra space.
Common Pitfalls
Forgetting That the First Element Cannot Be Reduced
The operation only moves value from position i to position i-1. This means you can never reduce the first element nums[0]} since there is no position to its left. Many solutions incorrectly assume you can redistribute values in both directions. The first element sets a lower bound on the answer since it can only increase, never decrease.
Integer Overflow in Prefix Sum Calculations
When computing prefix sums and comparing against maxVal * (i + 1), the multiplication can overflow if using 32-bit integers. For arrays with large values (up to 10^9) and indices up to 10^5, the product can exceed 2^31. Always use 64-bit integers (long, long long, Int64) for the prefix sum and the multiplication result.
Off-by-One Errors in the Ceiling Division
The greedy solution requires computing the ceiling of prefixSum / (i + 1). A common mistake is using integer division directly, which gives the floor. The ceiling can be computed as (prefixSum + i) / (i + 1) or using explicit ceiling functions. Also ensure the divisor is i + 1 (not i) since indices are 0-based but we need the count of elements.