Facebook Pixel

2064. Minimized Maximum of Products Distributed to Any Store

Problem Description

You have n retail stores and m different product types. Each product type has a certain quantity given in the array quantities, where quantities[i] represents how many items exist of product type i.

Your task is to distribute all products to the stores with these constraints:

  • Each store can receive products from only one product type (but can receive any amount from that type)
  • All products must be distributed
  • You want to minimize the maximum number of products any single store receives

The goal is to find the smallest possible value x, where x is the maximum number of products given to any store after distribution.

Example Understanding: If you have quantities = [11, 6] and n = 6 stores:

  • You could give the 11 items of type 1 to stores as: 4, 4, 3 (using 3 stores)
  • You could give the 6 items of type 2 to stores as: 3, 3 (using 2 stores)
  • Total stores used: 5 out of 6 available
  • Maximum products in any store: 4
  • This would give x = 4

The solution uses binary search to find the minimum possible maximum. The check function determines if it's possible to distribute all products such that no store gets more than x items. The expression (v + x - 1) // x calculates the minimum number of stores needed to distribute v items with at most x items per store (this is equivalent to ceil(v/x)). If the total stores needed is at most n, then x is feasible.

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

Intuition

The key insight is recognizing this as an optimization problem where we're trying to minimize the maximum value - a classic scenario for binary search on the answer.

Think about it this way: if we pick any value x as our maximum products per store, we can quickly check if it's possible to distribute all products without exceeding this limit. For each product type with quantity v, we need at least ceil(v/x) stores to distribute all v items with at most x items per store. If the total number of stores needed across all product types is at most n, then x is achievable.

Now, there's a monotonic property here: if we can distribute all products with maximum x items per store, we can definitely do it with x+1 items per store (we'd just need fewer stores). Conversely, if x doesn't work, neither will any value smaller than x.

This monotonic property makes binary search perfect. We search for the smallest x that works:

  • The minimum possible x is 1 (each store gets at least 1 item if we use all stores)
  • The maximum possible x is the largest quantity in our array (worst case: we put all items of one type in a single store)

For the feasibility check, the formula (v + x - 1) // x is a clever way to compute ceil(v/x) using integer arithmetic. This tells us how many stores we need to distribute v items with at most x items per store. We sum this across all product types and check if it's within our store limit n.

The binary search finds the boundary where the answer transitions from "not possible" to "possible", giving us the minimum feasible maximum.

Learn more about Greedy and Binary Search patterns.

Solution Approach

The solution implements a binary search to find the minimum possible maximum products per store.

Implementation Details:

  1. Binary Search Setup: We use bisect_left from Python's bisect module to search in the range [1, 10^6]. The range starts at 1 (minimum possible products per store) and goes up to 10^6 (a reasonable upper bound for the maximum quantity).

  2. Feasibility Check Function: The check(x) function determines if we can distribute all products with at most x items per store:

    def check(x):
        return sum((v + x - 1) // x for v in quantities) <= n
    • For each quantity v in quantities, we calculate (v + x - 1) // x
    • This formula computes ceil(v/x) using integer division, giving us the minimum number of stores needed for that product type
    • We sum the required stores for all product types
    • If the total is at most n, then x is feasible (returns True)
  3. Binary Search Execution:

    return 1 + bisect_left(range(1, 10**6), True, key=check)
    • bisect_left finds the leftmost position where True can be inserted in the sorted sequence
    • The key=check parameter means we're searching based on the feasibility check
    • Since check(x) returns False for small values and True for large values, bisect_left finds the transition point
    • We add 1 to the result because bisect_left returns an index (0-based), but our range starts at 1

Why This Works:

  • The check function creates a boolean sequence: [False, False, ..., False, True, True, ..., True]
  • The transition from False to True happens at the minimum feasible x
  • bisect_left efficiently finds this transition point in O(log(10^6)) time
  • Each check takes O(m) time where m is the length of quantities
  • Total time complexity: O(m * log(max_quantity))

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 a concrete example with quantities = [15, 10, 10] and n = 7 stores.

Step 1: Set up binary search range

  • Minimum possible x: 1 (each store gets at least 1 item)
  • Maximum possible x: 15 (the largest quantity)
  • We'll search in range [1, 15]

Step 2: Binary search iterations

Iteration 1: Try x = 8 (middle value)

  • Product type 1 (15 items): needs ceil(15/8) = 2 stores (8 + 7)
  • Product type 2 (10 items): needs ceil(10/8) = 2 stores (8 + 2)
  • Product type 3 (10 items): needs ceil(10/8) = 2 stores (8 + 2)
  • Total stores needed: 2 + 2 + 2 = 6 ≤ 7 ✓ (feasible)
  • Since it works, try smaller values (search left half)

Iteration 2: Try x = 4 (middle of [1, 7])

  • Product type 1 (15 items): needs ceil(15/4) = 4 stores (4 + 4 + 4 + 3)
  • Product type 2 (10 items): needs ceil(10/4) = 3 stores (4 + 4 + 2)
  • Product type 3 (10 items): needs ceil(10/4) = 3 stores (4 + 4 + 2)
  • Total stores needed: 4 + 3 + 3 = 10 > 7 ✗ (not feasible)
  • Since it doesn't work, try larger values (search right half)

Iteration 3: Try x = 6 (middle of [5, 7])

  • Product type 1 (15 items): needs ceil(15/6) = 3 stores (6 + 6 + 3)
  • Product type 2 (10 items): needs ceil(10/6) = 2 stores (6 + 4)
  • Product type 3 (10 items): needs ceil(10/6) = 2 stores (6 + 4)
  • Total stores needed: 3 + 2 + 2 = 7 ≤ 7 ✓ (feasible)
  • Try smaller values

Iteration 4: Try x = 5

  • Product type 1 (15 items): needs ceil(15/5) = 3 stores (5 + 5 + 5)
  • Product type 2 (10 items): needs ceil(10/5) = 2 stores (5 + 5)
  • Product type 3 (10 items): needs ceil(10/5) = 2 stores (5 + 5)
  • Total stores needed: 3 + 2 + 2 = 7 ≤ 7 ✓ (feasible)
  • Try smaller values one more time

Since x = 4 failed but x = 5 works, the answer is 5.

Final Distribution:

  • Store 1: 5 items (type 1)
  • Store 2: 5 items (type 1)
  • Store 3: 5 items (type 1)
  • Store 4: 5 items (type 2)
  • Store 5: 5 items (type 2)
  • Store 6: 5 items (type 3)
  • Store 7: 5 items (type 3)

Maximum items in any store: 5 ✓

Solution Implementation

1class Solution:
2    def minimizedMaximum(self, n: int, quantities: List[int]) -> int:
3        def can_distribute(max_products_per_store):
4            """
5            Check if we can distribute all products with at most 
6            max_products_per_store items per store
7          
8            For each product type, calculate minimum stores needed:
9            - Use ceiling division: (quantity + max - 1) // max
10            - This gives us ceil(quantity / max)
11            """
12            total_stores_needed = 0
13            for quantity in quantities:
14                # Calculate minimum stores needed for this product type
15                stores_for_this_product = (quantity + max_products_per_store - 1) // max_products_per_store
16                total_stores_needed += stores_for_this_product
17          
18            # Return True if we can fit all products in n stores
19            return total_stores_needed <= n
20      
21        # Binary search for the minimum possible maximum
22        # We search in range [1, max(quantities)]
23        # But using a simpler approach with bisect_left
24        from bisect import bisect_left
25      
26        # Find the first value where can_distribute returns True
27        # bisect_left finds the leftmost position where True should be inserted
28        # Since we want the first True value, we add 1 to the result
29        result = 1 + bisect_left(range(1, 10**6), True, key=can_distribute)
30      
31        return result
32
1class Solution {
2    public int minimizedMaximum(int n, int[] quantities) {
3        // Binary search range: minimum 1 item per store, maximum 100000 items per store
4        int left = 1;
5        int right = 100000;
6      
7        // Binary search to find the minimum possible maximum products per store
8        while (left < right) {
9            // Calculate middle value
10            int mid = (left + right) / 2;
11          
12            // Count total number of stores needed if each store can hold at most 'mid' items
13            int storesNeeded = 0;
14            for (int quantity : quantities) {
15                // Calculate stores needed for this product type
16                // Using ceiling division: (quantity + mid - 1) / mid equals ceil(quantity / mid)
17                storesNeeded += (quantity + mid - 1) / mid;
18            }
19          
20            // Check if we can distribute all products with at most 'mid' items per store
21            if (storesNeeded <= n) {
22                // If yes, try to find a smaller maximum (move search to left half)
23                right = mid;
24            } else {
25                // If no, we need a larger maximum (move search to right half)
26                left = mid + 1;
27            }
28        }
29      
30        // Return the minimum possible maximum products per store
31        return left;
32    }
33}
34
1class Solution {
2public:
3    int minimizedMaximum(int n, vector<int>& quantities) {
4        // Binary search range: minimum 1 item per store, maximum 100000 items per store
5        int left = 1;
6        int right = 100000;
7      
8        // Binary search to find the minimum possible maximum products per store
9        while (left < right) {
10            // Calculate middle value
11            int mid = left + (right - left) / 2;
12          
13            // Count total number of stores needed if max products per store is 'mid'
14            int storesNeeded = 0;
15            for (int& quantity : quantities) {
16                // Calculate stores needed for this product type
17                // Using ceiling division: (quantity + mid - 1) / mid
18                storesNeeded += (quantity + mid - 1) / mid;
19            }
20          
21            // Check if we can distribute with current maximum limit
22            if (storesNeeded <= n) {
23                // If stores needed <= available stores, try smaller maximum
24                right = mid;
25            } else {
26                // If stores needed > available stores, need larger maximum
27                left = mid + 1;
28            }
29        }
30      
31        // Return the minimum possible maximum products per store
32        return left;
33    }
34};
35
1/**
2 * Finds the minimized maximum number of products that can be distributed to n stores
3 * Uses binary search to find the optimal distribution value
4 * 
5 * @param n - Number of stores available
6 * @param quantities - Array of quantities for each product type
7 * @returns The minimized maximum number of products any store will receive
8 */
9function minimizedMaximum(n: number, quantities: number[]): number {
10    // Binary search bounds: minimum 1 product per store, maximum 100000
11    let minProductsPerStore: number = 1;
12    let maxProductsPerStore: number = 100000;
13  
14    // Binary search to find the minimum possible maximum products per store
15    while (minProductsPerStore < maxProductsPerStore) {
16        // Calculate middle value for binary search
17        const midProductsPerStore: number = (minProductsPerStore + maxProductsPerStore) >> 1;
18      
19        // Calculate total number of stores needed with current distribution limit
20        let requiredStores: number = 0;
21        for (const quantity of quantities) {
22            // Each product type needs ceil(quantity/mid) stores
23            requiredStores += Math.ceil(quantity / midProductsPerStore);
24        }
25      
26        // Check if we can distribute with current limit
27        if (requiredStores <= n) {
28            // If yes, try to find a smaller maximum (move search to left half)
29            maxProductsPerStore = midProductsPerStore;
30        } else {
31            // If no, we need a larger maximum (move search to right half)
32            minProductsPerStore = midProductsPerStore + 1;
33        }
34    }
35  
36    // Return the minimum possible maximum products per store
37    return minProductsPerStore;
38}
39

Time and Space Complexity

Time Complexity: O(m * log(max(quantities))) where m is the length of the quantities array.

  • The binary search using bisect_left searches over a range from 1 to 10^6, which takes O(log(10^6)) iterations.
  • For each iteration of the binary search, the check function is called.
  • The check function iterates through all elements in quantities to calculate the sum, which takes O(m) time.
  • Therefore, the overall time complexity is O(m * log(10^6)) which simplifies to O(m * log(max(quantities))) since 10^6 is used as an upper bound for the maximum possible value.

Space Complexity: O(1)

  • The check function uses only a constant amount of extra space for the sum calculation and loop variable.
  • The bisect_left function with a range object doesn't create the entire range in memory; it generates values on-the-fly, using O(1) space.
  • No additional data structures are created that scale with the input size.
  • Therefore, the space complexity is O(1) excluding the input array itself.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Incorrect Understanding of bisect_left Return Value

The most common pitfall is misunderstanding what bisect_left returns when used with a key function that produces a boolean sequence.

The Problem:

# Incorrect interpretation
result = bisect_left(range(1, 10**6), True, key=can_distribute)

Many assume this directly returns the minimum feasible value, but bisect_left returns an index into the range, not the actual value. Since range(1, 10**6) starts at 1, index 0 corresponds to value 1, index 1 to value 2, and so on.

The Fix:

# Correct: Add 1 to convert index to actual value
result = 1 + bisect_left(range(1, 10**6), True, key=can_distribute)

2. Using Wrong Search Bounds

Another pitfall is setting the upper bound too low or not considering edge cases.

The Problem:

# May fail if max(quantities) > 10000
result = 1 + bisect_left(range(1, 10000), True, key=can_distribute)

The Fix: Use either a safe upper bound or the actual maximum:

# Option 1: Safe upper bound
result = 1 + bisect_left(range(1, 10**6), True, key=can_distribute)

# Option 2: Exact bound (more efficient)
result = 1 + bisect_left(range(1, max(quantities) + 1), True, key=can_distribute)

3. Inverted Boolean Logic in Check Function

A subtle pitfall is inverting the boolean logic, searching for False instead of True.

The Problem:

def can_distribute(max_products):
    # Inverted: returns True when NOT feasible
    return sum((q + max_products - 1) // max_products for q in quantities) > n

# This creates sequence: [True, True, ..., False, False]
# bisect_left finds first False, but we want last True + 1

The Fix: Keep the logic straightforward - return True when the distribution is feasible:

def can_distribute(max_products):
    return sum((q + max_products - 1) // max_products for q in quantities) <= n

4. Integer Overflow in Ceiling Division

While less common in Python, the ceiling division formula can be confusing.

The Problem:

# Incorrect ceiling division attempts
stores_needed = v // x + (1 if v % x > 0 else 0)  # Verbose
stores_needed = math.ceil(v / x)  # Requires import, potential float precision issues

The Fix: Use the integer-only formula that's both efficient and precise:

stores_needed = (v + x - 1) // x  # Equivalent to ceil(v/x) using only integers
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

You are given an array of intervals where intervals[i] = [start_i, end_i] represent the start and end of the ith interval. You need to merge all overlapping intervals and return an array of the non-overlapping intervals that cover all the intervals in the input.


Recommended Readings

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

Load More