1760. Minimum Limit of Balls in a Bag
Problem Description
You have an array nums
where nums[i]
represents the number of balls in the i-th bag. You can perform up to maxOperations
operations to split bags.
In each operation, you can:
- Choose any bag with balls and divide it into two new bags
- Each new bag must contain a positive number of balls
- For example, a bag with 5 balls can be split into bags of 1 and 4 balls, or 2 and 3 balls
Your penalty is defined as the maximum number of balls in any single bag after all operations. The goal is to minimize this penalty.
The problem asks you to find the minimum possible penalty (the smallest possible maximum bag size) that can be achieved using at most maxOperations
splits.
For instance, if you have bags [9]
and maxOperations = 2
, you could:
- Split the bag of 9 into 6 and 3
- Split the bag of 6 into 3 and 3
- Result: bags of [3, 3, 3], giving a penalty of 3
The solution uses binary search to find the minimum penalty. It searches for the smallest maximum bag size that can be achieved within the operation limit. For each candidate maximum size mx
, it calculates how many splits are needed: for a bag with x
balls to have at most mx
balls per bag, you need (x - 1) // mx
splits. If the total splits needed is within maxOperations
, that maximum size is achievable.
Intuition
The key insight is recognizing that this is an optimization problem where we want to find the minimum possible maximum value. When we think about the relationship between the penalty (maximum bag size) and the number of operations needed, we notice an important pattern: as we allow larger maximum bag sizes, fewer operations are required.
Consider a bag with 9 balls. If we want the maximum to be 3, we need to split it into pieces of at most 3 balls each, requiring 2 operations. But if we allow the maximum to be 5, we only need 1 operation (splitting into 5 and 4). This inverse relationship suggests binary search could work.
The challenge becomes: given a target maximum bag size, how many operations do we need? For a bag with x
balls to be split into bags of at most mx
balls each, we need ceil(x/mx) - 1
operations. This can be written as (x - 1) // mx
operations. We sum this across all bags to get the total operations needed.
Since there's a monotonic relationship (smaller maximum requires more operations, larger maximum requires fewer operations), we can binary search on the answer. We search for the smallest maximum bag size where the required operations don't exceed maxOperations
.
The search space is from 1 (minimum possible bag size) to max(nums)
(no operations needed). For each candidate maximum in our binary search, we check if it's achievable within the operation limit. The smallest achievable maximum is our answer.
Learn more about Binary Search patterns.
Solution Approach
The solution implements a binary search to find the minimum possible maximum bag size. Here's how the implementation works:
Binary Search Setup:
- Left boundary
l = 1
: The minimum possible bag size is 1 ball - Right boundary
r = max(nums)
: If we don't perform any operations, the maximum is the largest original bag
Helper Function check(mx)
:
This function determines if we can achieve a maximum bag size of mx
within maxOperations
:
- For each bag with
x
balls, we calculate how many splits are needed:(x - 1) // mx
- This formula works because to split
x
balls into bags of at mostmx
balls, we needceil(x/mx) - 1
operations - Sum all required operations and check if the total is ≤
maxOperations
Binary Search Process:
The code uses Python's bisect_left
with a custom key function:
bisect_left(range(1, max(nums) + 1), True, key=check) + 1
This searches for the leftmost position where check(mx)
returns True
. The search works as follows:
- For each middle value
mid
in the range[1, max(nums)]
- If
check(mid)
returnsTrue
, the valuemid
is achievable, so we search for smaller values - If
check(mid)
returnsFalse
, we need a larger maximum, so we search higher values - The process continues until we find the smallest value where
check
returnsTrue
Time Complexity: O(n * log(max(nums)))
where n
is the length of the array. We perform binary search over the range [1, max(nums)]
, and for each iteration, we check all n
bags.
Space Complexity: O(1)
as we only use a constant amount of extra space.
The + 1
at the end adjusts for the fact that bisect_left
returns an index starting from 0, while our range starts from 1.
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 = [7, 17]
and maxOperations = 2
.
Step 1: Initialize Binary Search Range
- Left boundary:
l = 1
(minimum possible bag size) - Right boundary:
r = max([7, 17]) = 17
(maximum without any operations) - We're searching for the smallest maximum bag size in range
[1, 17]
Step 2: Binary Search Process
First Iteration - Try mid = 9
:
- Check if we can achieve maximum bag size of 9 with ≤ 2 operations
- Bag with 7 balls: needs
(7-1)//9 = 0
operations (already ≤ 9) - Bag with 17 balls: needs
(17-1)//9 = 1
operation (split into 9 and 8) - Total operations needed:
0 + 1 = 1 ≤ 2
✓ - Since this works, try smaller maximum (search left half): range becomes
[1, 8]
Second Iteration - Try mid = 4
:
- Check if we can achieve maximum bag size of 4 with ≤ 2 operations
- Bag with 7 balls: needs
(7-1)//4 = 1
operation (split into 4 and 3) - Bag with 17 balls: needs
(17-1)//4 = 4
operations (need to split into 5 pieces) - Total operations needed:
1 + 4 = 5 > 2
✗ - This doesn't work, need larger maximum (search right half): range becomes
[5, 8]
Third Iteration - Try mid = 6
:
- Check if we can achieve maximum bag size of 6 with ≤ 2 operations
- Bag with 7 balls: needs
(7-1)//6 = 1
operation (split into 6 and 1) - Bag with 17 balls: needs
(17-1)//6 = 2
operations (split into 6, 6, and 5) - Total operations needed:
1 + 2 = 3 > 2
✗ - Still doesn't work, search right half: range becomes
[7, 8]
Fourth Iteration - Try mid = 7
:
- Check if we can achieve maximum bag size of 7 with ≤ 2 operations
- Bag with 7 balls: needs
(7-1)//7 = 0
operations (already = 7) - Bag with 17 balls: needs
(17-1)//7 = 2
operations (split into 7, 7, and 3) - Total operations needed:
0 + 2 = 2 ≤ 2
✓ - This works, but check if smaller is possible: range becomes
[7, 7]
Result: The minimum possible penalty is 7.
We can verify: With 2 operations, we split the 17-ball bag twice to get [7, 7, 3]
, combined with the original 7-ball bag gives us [7, 7, 7, 3]
. The maximum is 7.
Solution Implementation
1class Solution:
2 def minimumSize(self, nums: List[int], maxOperations: int) -> int:
3 def can_achieve_max_size(max_size: int) -> bool:
4 """
5 Check if we can make all bags have size <= max_size
6 with at most maxOperations splits.
7
8 For each bag of size x, we need (x - 1) // max_size operations
9 to split it into bags of size <= max_size.
10 """
11 total_operations_needed = 0
12 for bag_size in nums:
13 # Calculate operations needed to split this bag
14 operations_for_this_bag = (bag_size - 1) // max_size
15 total_operations_needed += operations_for_this_bag
16
17 # Return True if achievable within operation limit
18 return total_operations_needed <= maxOperations
19
20 # Binary search for the minimum possible maximum bag size
21 # Search range: from 1 to the largest bag size
22 left = 1
23 right = max(nums)
24
25 # Find the smallest max_size where can_achieve_max_size returns True
26 while left < right:
27 mid = (left + right) // 2
28 if can_achieve_max_size(mid):
29 # If achievable, try smaller max_size
30 right = mid
31 else:
32 # If not achievable, need larger max_size
33 left = mid + 1
34
35 return left
36
1class Solution {
2 public int minimumSize(int[] nums, int maxOperations) {
3 // Binary search on the answer: find minimum possible max value in a bag
4 // Left boundary: minimum possible value is 1
5 int left = 1;
6 // Right boundary: maximum possible value is the largest number in array
7 int right = Arrays.stream(nums).max().getAsInt();
8
9 // Binary search to find the minimum penalty
10 while (left < right) {
11 // Calculate middle value as potential maximum bag size
12 int mid = (left + right) >> 1; // Equivalent to (left + right) / 2
13
14 // Count total operations needed if we want max bag size to be 'mid'
15 long totalOperations = 0;
16 for (int num : nums) {
17 // For each number, calculate how many splits needed
18 // (num - 1) / mid gives the minimum splits to ensure no bag > mid
19 // Example: if num=9 and mid=3, we need (9-1)/3 = 2 operations
20 // This splits 9 into [3, 3, 3]
21 totalOperations += (num - 1) / mid;
22 }
23
24 // Check if we can achieve this max bag size within allowed operations
25 if (totalOperations <= maxOperations) {
26 // If yes, try to find a smaller maximum (move right boundary left)
27 right = mid;
28 } else {
29 // If no, we need a larger maximum (move left boundary right)
30 left = mid + 1;
31 }
32 }
33
34 // Return the minimum possible maximum bag size
35 return left;
36 }
37}
38
1class Solution {
2public:
3 int minimumSize(vector<int>& nums, int maxOperations) {
4 // Binary search on the answer: find minimum possible maximum bag size
5 // Left boundary: minimum possible size is 1
6 int left = 1;
7 // Right boundary: maximum possible size is the largest element (no operations)
8 int right = ranges::max(nums);
9
10 // Binary search to find the minimum penalty
11 while (left < right) {
12 // Calculate middle value (potential maximum bag size)
13 int mid = (left + right) >> 1;
14
15 // Count total operations needed if maximum bag size is 'mid'
16 long long totalOperations = 0;
17 for (int num : nums) {
18 // For each bag, calculate splits needed to ensure all parts <= mid
19 // Formula: (num - 1) / mid gives minimum splits required
20 totalOperations += (num - 1) / mid;
21 }
22
23 // Check if we can achieve this maximum bag size within allowed operations
24 if (totalOperations <= maxOperations) {
25 // Can achieve 'mid' or smaller, try to minimize further
26 right = mid;
27 } else {
28 // Need more operations than allowed, increase the bag size
29 left = mid + 1;
30 }
31 }
32
33 // Return the minimum possible maximum bag size
34 return left;
35 }
36};
37
1/**
2 * Finds the minimum possible maximum value in a bag after performing at most maxOperations splits
3 * Uses binary search to find the optimal penalty value
4 *
5 * @param nums - Array of integers representing balls in bags
6 * @param maxOperations - Maximum number of split operations allowed
7 * @returns The minimum possible penalty (maximum balls in any bag)
8 */
9function minimumSize(nums: number[], maxOperations: number): number {
10 // Initialize binary search boundaries
11 // left: minimum possible penalty is 1 (at least 1 ball per bag)
12 // right: maximum possible penalty is the largest number in nums (no operations performed)
13 let left: number = 1;
14 let right: number = Math.max(...nums);
15
16 // Binary search for the minimum penalty
17 while (left < right) {
18 // Calculate middle value for current search range
19 const middle: number = Math.floor((left + right) / 2);
20
21 // Calculate total operations needed to achieve penalty of 'middle'
22 // For each bag with x balls, we need Math.floor((x - 1) / middle) operations
23 // to split it into bags with at most 'middle' balls each
24 const totalOperationsNeeded: number = nums
25 .map((ballCount: number) => Math.floor((ballCount - 1) / middle))
26 .reduce((sum: number, operations: number) => sum + operations, 0);
27
28 // Check if we can achieve this penalty within the operation limit
29 if (totalOperationsNeeded <= maxOperations) {
30 // We can achieve this penalty, try to find a smaller one
31 right = middle;
32 } else {
33 // We cannot achieve this penalty, need a larger penalty value
34 left = middle + 1;
35 }
36 }
37
38 // Return the minimum achievable penalty
39 return left;
40}
41
Time and Space Complexity
The time complexity is O(n × log M)
, where n
is the length of the array nums
and M
is the maximum value in the array nums
.
This complexity arises from:
- Binary search over the range
[1, max(nums)]
, which hasM
elements, takingO(log M)
iterations - For each binary search iteration, the
check
function is called, which iterates through alln
elements innums
to computesum((x - 1) // mx for x in nums)
, takingO(n)
time - Total:
O(n) × O(log M) = O(n × log M)
The space complexity is O(1)
.
The algorithm only uses a constant amount of extra space for variables in the check
function and the binary search operation. The range
object in Python is a lazy iterator that doesn't create the entire list in memory, so it doesn't contribute to space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Split Calculation Formula
Pitfall: A common mistake is using the wrong formula to calculate the number of operations needed to split a bag. Developers might incorrectly use:
x // mx
(forgetting to subtract 1)(x + mx - 1) // mx
(this gives the number of resulting bags, not operations)x // mx - 1
(off by one when x is divisible by mx)
Why the formula (x - 1) // mx
works:
- To split x balls into bags of at most mx balls each, you need
ceil(x/mx)
total bags - Since you start with 1 bag, you need
ceil(x/mx) - 1
operations - The formula
(x - 1) // mx
is equivalent toceil(x/mx) - 1
Example:
- With 9 balls and max size 3:
(9-1)//3 = 8//3 = 2
operations needed - This creates 3 bags of 3 balls each (9 → 6+3 → 3+3+3)
2. Binary Search Boundary Issues
Pitfall: Setting incorrect search boundaries or updating them improperly:
- Starting with
left = 0
instead ofleft = 1
(bag size must be positive) - Using
left = mid
instead ofleft = mid + 1
when the current max_size is not achievable - Using
right = mid - 1
instead ofright = mid
when the current max_size is achievable
Correct approach:
while left < right: mid = (left + right) // 2 if can_achieve_max_size(mid): right = mid # Keep mid as a candidate else: left = mid + 1 # Mid is too small, exclude it
3. Integer Overflow in Other Languages
Pitfall: When translating to languages like C++ or Java, the sum of operations might overflow for large inputs.
Solution: Use appropriate data types:
// C++ example long long total_operations = 0; // Use long long instead of int for (int bag_size : nums) { total_operations += (bag_size - 1) / max_size; }
4. Misunderstanding the Problem Goal
Pitfall: Confusing the objective - trying to minimize the total number of balls or the number of bags, rather than minimizing the maximum bag size.
Clarification: The goal is to minimize the penalty, which is the size of the largest bag after all operations. We're looking for the smallest possible value of the maximum bag size.
5. Edge Case Handling
Pitfall: Not considering edge cases properly:
- When
maxOperations = 0
(no splits allowed, answer ismax(nums)
) - When all bags have size 1 (no splits needed, answer is 1)
- When
maxOperations
is very large (answer approaches 1)
Robust solution includes these checks implicitly through the binary search range and operation counting.
Which of the two traversal algorithms (BFS and DFS) can be used to find whether two nodes are connected?
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!