Facebook Pixel

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.

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

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 most mx balls, we need ceil(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:

  1. For each middle value mid in the range [1, max(nums)]
  2. If check(mid) returns True, the value mid is achievable, so we search for smaller values
  3. If check(mid) returns False, we need a larger maximum, so we search higher values
  4. The process continues until we find the smallest value where check returns True

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 Evaluator

Example 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 has M elements, taking O(log M) iterations
  • For each binary search iteration, the check function is called, which iterates through all n elements in nums to compute sum((x - 1) // mx for x in nums), taking O(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 to ceil(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 of left = 1 (bag size must be positive)
  • Using left = mid instead of left = mid + 1 when the current max_size is not achievable
  • Using right = mid - 1 instead of right = 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 is max(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.

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

Which of the two traversal algorithms (BFS and DFS) can be used to find whether two nodes are connected?


Recommended Readings

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

Load More