class Solution:
def minimumSize(self, nums, maxOperations):
def can_split(limit):
ops = 0
for balls in nums:
# Each bag needs (balls-1)//limit splits (integer division)
ops += (balls - 1) // limit
return ops <= maxOperations
left, right = 1, max(nums)
answer = right
while left <= right:
mid = (left + right) // 2
if can_split(mid):
answer = mid
right = mid - 1
else:
left = mid + 1
return answer
class Solution {
public:
bool canSplit(vector<int>& nums, int maxOperations, int limit) {
int ops = 0;
for (int balls : nums) {
ops += (balls - 1) / limit;
}
return ops <= maxOperations;
}
int minimumSize(vector<int>& nums, int maxOperations) {
int left = 1, right = *max_element(nums.begin(), nums.end());
int answer = right;
while (left <= right) {
int mid = left + (right - left) / 2;
if (canSplit(nums, maxOperations, mid)) {
answer = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return answer;
}
};
class Solution {
public int minimumSize(int[] nums, int maxOperations) {
int left = 1, right = 0;
for (int num : nums) right = Math.max(right, num);
int answer = right;
while (left <= right) {
int mid = left + (right - left) / 2;
if (canSplit(nums, maxOperations, mid)) {
answer = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return answer;
}
private boolean canSplit(int[] nums, int maxOperations, int limit) {
int ops = 0;
for (int balls : nums) {
ops += (balls - 1) / limit;
}
return ops <= maxOperations;
}
}
var minimumSize = function(nums, maxOperations) {
const canSplit = (limit) => {
let ops = 0;
for (let balls of nums) {
ops += Math.floor((balls - 1) / limit);
}
return ops <= maxOperations;
};
let left = 1, right = Math.max(...nums);
let answer = right;
while (left <= right) {
let mid = Math.floor((left + right) / 2);
if (canSplit(mid)) {
answer = mid;
right = mid - 1;
} else {
left = mid + 1;
}
}
return answer;
};
You are given an array nums
where each element represents the number of balls in a bag. You can perform up to maxOperations
operations. In each operation, you may take any bag and split it into two bags with a positive number of balls in each (the sum remains the same). The goal is to minimize the largest number of balls in any single bag after performing at most maxOperations
splits.
maxOperations
.At first glance, the problem seems to ask for distributing balls as evenly as possible. The brute-force way would be to try all possible ways to split the bags, but this quickly becomes infeasible with large arrays or big numbers.
The key insight is to recognize that the problem is about minimizing the maximum bag size using a limited number of splits. Instead of tracking every possible distribution, we can reframe the problem: for a given maximum allowed bag size, can we achieve it with at most maxOperations
splits? If so, maybe we can do even better; if not, we need to allow larger bags.
This is a classic scenario for binary search on the answer. We search for the smallest possible maximum bag size that can be achieved with the allowed number of operations.
We use binary search to efficiently find the minimum possible limit for the largest bag size:
nums
(no splits at all).maxOperations
splits.balls
balls and candidate size limit
, the number of splits needed is (balls - 1) // limit
. This formula works because splitting a bag into ceil(balls / limit)
bags requires ceil(balls / limit) - 1
splits, and (balls - 1) // limit
gives the same result.maxOperations
, it's feasible.
Suppose nums = [9, 7, 3]
and maxOperations = 2
.
After at most 2 splits, the smallest possible largest bag is 5.
O(log(max(nums)))
iterations.O(n)
per check.O(n * log(max(nums)))
.O(1)
extra space (not counting input).By reframing the problem as a search for the minimal possible maximum bag size, we avoid brute-force enumeration and instead use binary search. The key insight is that for any candidate bag size, we can efficiently check if it is achievable with the allowed number of splits. This makes the solution both elegant and efficient, scaling well even for large input sizes.