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 Implementation

1class Solution:
2    def minimumSize(self, nums: List[int], maxOperations: int) -> int:
3        def feasible(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                total_operations_needed += (bag_size - 1) // max_size
14            return total_operations_needed <= maxOperations
15
16        # Binary search for the minimum possible maximum bag size
17        left, right = 1, max(nums)
18        first_true_index = -1
19
20        while left <= right:
21            mid = (left + right) // 2
22            if feasible(mid):
23                first_true_index = mid
24                right = mid - 1
25            else:
26                left = mid + 1
27
28        return first_true_index
29
1class Solution {
2    public int minimumSize(int[] nums, int maxOperations) {
3        int left = 1;
4        int right = Arrays.stream(nums).max().getAsInt();
5        int firstTrueIndex = -1;
6
7        while (left <= right) {
8            int mid = left + (right - left) / 2;
9            if (feasible(nums, mid, maxOperations)) {
10                firstTrueIndex = mid;
11                right = mid - 1;
12            } else {
13                left = mid + 1;
14            }
15        }
16
17        return firstTrueIndex;
18    }
19
20    private boolean feasible(int[] nums, int maxSize, int maxOperations) {
21        long totalOperations = 0;
22        for (int num : nums) {
23            totalOperations += (num - 1) / maxSize;
24        }
25        return totalOperations <= maxOperations;
26    }
27}
28
1class Solution {
2public:
3    int minimumSize(vector<int>& nums, int maxOperations) {
4        int left = 1;
5        int right = ranges::max(nums);
6        int firstTrueIndex = -1;
7
8        while (left <= right) {
9            int mid = left + (right - left) / 2;
10            if (feasible(nums, mid, maxOperations)) {
11                firstTrueIndex = mid;
12                right = mid - 1;
13            } else {
14                left = mid + 1;
15            }
16        }
17
18        return firstTrueIndex;
19    }
20
21private:
22    bool feasible(vector<int>& nums, int maxSize, int maxOperations) {
23        long long totalOperations = 0;
24        for (int num : nums) {
25            totalOperations += (num - 1) / maxSize;
26        }
27        return totalOperations <= maxOperations;
28    }
29};
30
1function minimumSize(nums: number[], maxOperations: number): number {
2    const feasible = (maxSize: number): boolean => {
3        let totalOperations = 0;
4        for (const num of nums) {
5            totalOperations += Math.floor((num - 1) / maxSize);
6        }
7        return totalOperations <= maxOperations;
8    };
9
10    let left = 1;
11    let right = Math.max(...nums);
12    let firstTrueIndex = -1;
13
14    while (left <= right) {
15        const mid = Math.floor((left + right) / 2);
16        if (feasible(mid)) {
17            firstTrueIndex = mid;
18            right = mid - 1;
19        } else {
20            left = mid + 1;
21        }
22    }
23
24    return firstTrueIndex;
25}
26

Solution Approach

The solution implements a binary search to find the minimum possible maximum bag size. This is a classic "find first true" binary search pattern.

Binary Search Setup:

  • Left boundary left = 1: The minimum possible bag size is 1 ball
  • Right boundary right = max(nums): If we don't perform any operations, the maximum is the largest original bag
  • first_true_index = -1: Tracks the minimum achievable maximum bag size

Feasibility Check can_achieve_max_size(max_size): This function determines if we can achieve a maximum bag size of max_size within maxOperations:

  • For each bag with x balls, we calculate how many splits are needed: (x - 1) // max_size
  • This formula works because to split x balls into bags of at most max_size balls, we need ceil(x/max_size) - 1 operations
  • Sum all required operations and check if the total is ≤ maxOperations
  • Returns True if achievable (feasible), False otherwise

Binary Search Template: The algorithm follows the standard "find first true" template:

left, right = 1, max(nums)
first_true_index = -1

while left <= right:
    mid = (left + right) // 2
    if can_achieve_max_size(mid):
        first_true_index = mid
        right = mid - 1  # Try to find smaller valid size
    else:
        left = mid + 1   # Need larger size

return first_true_index

The key characteristics of this template:

  1. Uses while left <= right (not left < right)
  2. When feasible, records first_true_index = mid and searches left with right = mid - 1
  3. When not feasible, searches right with left = mid + 1
  4. Returns first_true_index which holds the smallest valid maximum size

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.

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

  • left = 1 (minimum possible bag size)
  • right = max([7, 17]) = 17 (maximum without any operations)
  • first_true_index = -1 (tracks the answer)

Step 2: Binary Search Process with Template

Iteration 1: left=1, right=17, mid=9

  • Check feasible(9):
    • Bag with 7 balls: (7-1)//9 = 0 operations
    • Bag with 17 balls: (17-1)//9 = 1 operation
    • Total: 0 + 1 = 1 ≤ 2feasible(9) = True
  • Since feasible: first_true_index = 9, right = 9 - 1 = 8

Iteration 2: left=1, right=8, mid=4

  • Check feasible(4):
    • Bag with 7 balls: (7-1)//4 = 1 operation
    • Bag with 17 balls: (17-1)//4 = 4 operations
    • Total: 1 + 4 = 5 > 2feasible(4) = False
  • Since not feasible: left = 4 + 1 = 5

Iteration 3: left=5, right=8, mid=6

  • Check feasible(6):
    • Bag with 7 balls: (7-1)//6 = 1 operation
    • Bag with 17 balls: (17-1)//6 = 2 operations
    • Total: 1 + 2 = 3 > 2feasible(6) = False
  • Since not feasible: left = 6 + 1 = 7

Iteration 4: left=7, right=8, mid=7

  • Check feasible(7):
    • Bag with 7 balls: (7-1)//7 = 0 operations
    • Bag with 17 balls: (17-1)//7 = 2 operations
    • Total: 0 + 2 = 2 ≤ 2feasible(7) = True
  • Since feasible: first_true_index = 7, right = 7 - 1 = 6

Iteration 5: left=7, right=6

  • left > right, loop terminates

Result: Return first_true_index = 7

The minimum possible penalty is 7. 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.

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. Using Wrong Binary Search Template Variant

Pitfall: Using while left < right with right = mid instead of the correct template:

# WRONG template variant
while left < right:
    mid = (left + right) // 2
    if feasible(mid):
        right = mid
    else:
        left = mid + 1
return left

Correct template:

left, right = 1, 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

Key differences:

  • Uses while left <= right (not left < right)
  • Tracks the answer in first_true_index
  • Uses right = mid - 1 when feasible (not right = mid)

2. Incorrect Split Calculation Formula

Pitfall: Using the wrong formula to calculate the number of operations needed:

  • 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)

3. Binary Search Boundary Issues

Pitfall: Setting incorrect search boundaries:

  • Starting with left = 0 instead of left = 1 (bag size must be positive)
  • Not initializing first_true_index = -1 to handle edge cases

Correct initialization:

left, right = 1, max(nums)
first_true_index = -1

4. 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:

long long totalOperations = 0;  // Use long long instead of int
for (int num : nums) {
    totalOperations += (num - 1) / maxSize;
}

5. Misunderstanding the Feasible Function

Pitfall: The feasible function should return True when the condition is satisfied (we CAN achieve the target with given operations), creating a pattern like:

false, false, false, true, true, true, ...

The binary search finds the first true (minimum feasible max size).

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

Which of the following shows the order of node visit in a Breadth-first Search?


Recommended Readings

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

Load More