Facebook Pixel

1889. Minimum Space Wasted From Packaging

Problem Description

You need to pack n packages into boxes, with exactly one package per box. There are m different suppliers, and each supplier produces boxes of various sizes with unlimited quantity available.

Given:

  • An array packages where packages[i] represents the size of the i-th package
  • A 2D array boxes where boxes[j] contains all the different box sizes that supplier j can provide

Rules:

  • A package can only fit in a box if the package size ≤ box size
  • You must choose exactly ONE supplier and use only their boxes
  • Each package must go into a separate box

The goal is to minimize the total wasted space, where:

  • Wasted space for one package = box size - package size
  • Total wasted space = sum of wasted space across all packages

For example, with packages [2,3,5] and a supplier offering boxes [4,8]:

  • Package size 2 → box size 4 (waste = 4-2 = 2)
  • Package size 3 → box size 4 (waste = 4-3 = 1)
  • Package size 5 → box size 8 (waste = 8-5 = 3)
  • Total waste = 2 + 1 + 3 = 6

Return:

  • The minimum total wasted space when choosing the optimal supplier
  • -1 if no supplier can accommodate all packages
  • Since the answer can be large, return it modulo 10^9 + 7
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

The key insight is that we need to try each supplier independently and find which one gives us the minimum waste. Since we can only choose one supplier, we need to check if that supplier can accommodate all packages first.

For each supplier, the optimal strategy is to match each package with the smallest possible box that can fit it. This minimizes waste per package. If we sort both packages and boxes, we can efficiently assign packages to boxes using a greedy approach.

Consider sorted packages [2, 3, 5] and sorted boxes [4, 8]:

  • All packages with size ≤ 4 should go in box size 4
  • All remaining packages with size ≤ 8 should go in box size 8

This suggests we can process boxes in ascending order. For each box size b, we find all packages that:

  1. Haven't been assigned yet
  2. Can fit in this box (size ≤ b)

These packages should all use this box size since it's the smallest available box that fits them.

To efficiently find which packages fit in each box size, we can use binary search. After sorting packages, bisect_right(packages, b) tells us the rightmost position where we could insert b, which gives us the count of packages ≤ b.

The total space used by a supplier equals the sum of all box sizes used. The waste is then total_space_used - sum(packages). Since sum(packages) is constant regardless of supplier choice, minimizing total space used minimizes waste.

We process each supplier, calculate their total space usage, track the minimum, and return the corresponding waste. If no supplier can fit all packages (detected when the largest package exceeds the supplier's largest box), we skip that supplier.

Learn more about Binary Search, Prefix Sum and Sorting patterns.

Solution Approach

The implementation follows these steps:

  1. Initialize variables: Set mod = 10^9 + 7 for the modulo operation, and ans = inf to track the minimum total space used across all suppliers.

  2. Sort packages: packages.sort() enables us to use binary search and ensures we can check if a supplier can accommodate all packages by comparing the largest package with the supplier's largest box.

  3. Process each supplier: For each supplier's box list:

    • Sort boxes: box.sort() allows us to process boxes from smallest to largest
    • Early termination check: If packages[-1] > box[-1] (largest package > largest box), skip this supplier as it cannot accommodate all packages
  4. Calculate total space for current supplier:

    • Initialize s = 0 (total space used) and i = 0 (starting index for packages)
    • For each box size b in ascending order:
      • Use j = bisect_right(packages, b, lo=i) to find how many packages from index i onwards can fit in box size b
      • These are packages in range [i, j) that haven't been assigned yet and have size ≤ b
      • Add space used: s += (j - i) * b (number of packages × box size)
      • Update starting position: i = j for the next iteration
  5. Track minimum: Update ans = min(ans, s) to keep the minimum total space across all suppliers

  6. Calculate final result:

    • If ans == inf, no supplier could accommodate all packages, return -1
    • Otherwise, the waste is total_space_used - total_package_size
    • Return (ans - sum(packages)) % mod

The binary search optimization is crucial: bisect_right(packages, b, lo=i) finds packages in range [i, n) with size ≤ b in O(log n) time. The lo=i parameter ensures we only search among unassigned packages, making each package get assigned exactly once across all boxes of a supplier.

Time complexity: O(n log n + m * k * log n) where n is number of packages, m is number of suppliers, and k is average number of box sizes per supplier.

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 packages [3, 5, 8, 10, 11, 12] and two suppliers:

  • Supplier 0: boxes [15, 10, 20]
  • Supplier 1: boxes [12, 11, 8, 9]

Step 1: Sort packages Packages after sorting: [3, 5, 8, 10, 11, 12]

Step 2: Try Supplier 0 Sort boxes: [10, 15, 20] Check if feasible: largest package (12) ≤ largest box (20) ✓

Process each box size:

  • Box size 10 (i=0):

    • Binary search: packages ≤ 10 are at indices [0,4) → packages [3, 5, 8, 10]
    • Space used: 4 packages × 10 = 40
    • Update i=4
  • Box size 15 (i=4):

    • Binary search: packages ≤ 15 from index 4 are at indices [4,6) → packages [11, 12]
    • Space used: 2 packages × 15 = 30
    • Update i=6
  • Box size 20 (i=6):

    • No remaining packages
    • Space used: 0

Total space for Supplier 0: 40 + 30 = 70

Step 3: Try Supplier 1 Sort boxes: [8, 9, 11, 12] Check if feasible: largest package (12) ≤ largest box (12) ✓

Process each box size:

  • Box size 8 (i=0):

    • Binary search: packages ≤ 8 are at indices [0,3) → packages [3, 5, 8]
    • Space used: 3 packages × 8 = 24
    • Update i=3
  • Box size 9 (i=3):

    • Binary search: packages ≤ 9 from index 3 are at indices [3,3) → no packages
    • Space used: 0
    • Update i=3
  • Box size 11 (i=3):

    • Binary search: packages ≤ 11 from index 3 are at indices [3,5) → packages [10, 11]
    • Space used: 2 packages × 11 = 22
    • Update i=5
  • Box size 12 (i=5):

    • Binary search: packages ≤ 12 from index 5 are at indices [5,6) → package [12]
    • Space used: 1 package × 12 = 12
    • Update i=6

Total space for Supplier 1: 24 + 22 + 12 = 58

Step 4: Calculate minimum waste

  • Supplier 0 total space: 70
  • Supplier 1 total space: 58 (minimum)
  • Sum of packages: 3 + 5 + 8 + 10 + 11 + 12 = 49
  • Minimum waste: 58 - 49 = 9

Return: 9

Solution Implementation

1from typing import List
2from math import inf
3from bisect import bisect_right
4
5class Solution:
6    def minWastedSpace(self, packages: List[int], boxes: List[List[int]]) -> int:
7        MOD = 10**9 + 7
8        min_wasted_space = inf
9      
10        # Sort packages to enable binary search
11        packages.sort()
12      
13        # Try each supplier's box sizes
14        for box_sizes in boxes:
15            # Sort current supplier's box sizes
16            box_sizes.sort()
17          
18            # Skip this supplier if their largest box can't fit the largest package
19            if packages[-1] > box_sizes[-1]:
20                continue
21          
22            # Calculate total space used by this supplier's boxes
23            total_space_used = 0
24            package_index = 0
25          
26            # For each box size, find how many packages it will contain
27            for box_size in box_sizes:
28                # Find the rightmost package that fits in current box size
29                next_package_index = bisect_right(packages, box_size, lo=package_index)
30              
31                # Add space used: number of packages * box size
32                packages_count = next_package_index - package_index
33                total_space_used += packages_count * box_size
34              
35                # Update starting position for next box size
36                package_index = next_package_index
37          
38            # Update minimum total space used
39            min_wasted_space = min(min_wasted_space, total_space_used)
40      
41        # If no valid supplier found, return -1
42        if min_wasted_space == inf:
43            return -1
44      
45        # Calculate wasted space: total space used - actual package sizes
46        total_package_size = sum(packages)
47        wasted_space = (min_wasted_space - total_package_size) % MOD
48      
49        return wasted_space
50
1class Solution {
2    public int minWastedSpace(int[] packages, int[][] boxes) {
3        int packageCount = packages.length;
4        final long INFINITY = 1L << 62;
5      
6        // Sort packages in ascending order for binary search
7        Arrays.sort(packages);
8      
9        long minTotalSpace = INFINITY;
10      
11        // Try each supplier's box collection
12        for (int[] boxSizes : boxes) {
13            // Sort current supplier's boxes in ascending order
14            Arrays.sort(boxSizes);
15          
16            // Skip this supplier if their largest box can't fit the largest package
17            if (packages[packageCount - 1] > boxSizes[boxSizes.length - 1]) {
18                continue;
19            }
20          
21            // Calculate total space used for this supplier
22            long totalSpaceUsed = 0;
23            int packageStartIndex = 0;
24          
25            // For each box size, assign packages that fit
26            for (int boxSize : boxSizes) {
27                // Find the index of first package that doesn't fit in current box
28                int packageEndIndex = findFirstPackageIndexExceedingSize(packages, boxSize, packageStartIndex);
29              
30                // All packages from startIndex to endIndex-1 will use this box size
31                // Add total space: (number of packages) * (box size)
32                totalSpaceUsed += (long)(packageEndIndex - packageStartIndex) * boxSize;
33              
34                // Move to next unassigned package
35                packageStartIndex = packageEndIndex;
36            }
37          
38            // Update minimum total space needed
39            minTotalSpace = Math.min(minTotalSpace, totalSpaceUsed);
40        }
41      
42        // If no supplier can accommodate all packages
43        if (minTotalSpace == INFINITY) {
44            return -1;
45        }
46      
47        // Calculate total package size
48        long totalPackageSize = 0;
49        for (int packageSize : packages) {
50            totalPackageSize += packageSize;
51        }
52      
53        // Calculate wasted space and return modulo result
54        final int MODULO = (int)1e9 + 7;
55        long wastedSpace = minTotalSpace - totalPackageSize;
56        return (int)(wastedSpace % MODULO);
57    }
58  
59    /**
60     * Binary search to find the first package index where package size exceeds the given limit
61     * @param sortedPackages Array of packages sorted in ascending order
62     * @param sizeLimit Maximum size to compare against
63     * @param startIndex Starting index for the search
64     * @return Index of first package exceeding sizeLimit, or array length if all fit
65     */
66    private int findFirstPackageIndexExceedingSize(int[] sortedPackages, int sizeLimit, int startIndex) {
67        int left = startIndex;
68        int right = sortedPackages.length;
69      
70        while (left < right) {
71            int mid = (left + right) >> 1;
72          
73            if (sortedPackages[mid] > sizeLimit) {
74                // Package at mid exceeds limit, search in left half
75                right = mid;
76            } else {
77                // Package at mid fits, search in right half
78                left = mid + 1;
79            }
80        }
81      
82        return left;
83    }
84}
85
1class Solution {
2public:
3    int minWastedSpace(vector<int>& packages, vector<vector<int>>& boxes) {
4        int packageCount = packages.size();
5        int supplierCount = boxes.size();
6      
7        // Sort packages in ascending order for binary search
8        sort(packages.begin(), packages.end());
9      
10        const int MOD = 1e9 + 7;
11        const long long INF = 1LL << 62;  // Large value representing impossible case
12        long long minTotalBoxSpace = INF;
13      
14        // Try each supplier's box sizes
15        for (auto& boxSizes : boxes) {
16            // Sort current supplier's box sizes for greedy allocation
17            sort(boxSizes.begin(), boxSizes.end());
18          
19            // Skip this supplier if their largest box can't fit the largest package
20            if (packages.back() > boxSizes.back()) {
21                continue;
22            }
23          
24            int packageIndex = 0;
25            long long totalBoxSpace = 0;
26          
27            // For each box size, assign all packages that fit
28            for (auto& currentBoxSize : boxSizes) {
29                // Find the first package that doesn't fit in current box size
30                int nextPackageIndex = upper_bound(packages.begin() + packageIndex, 
31                                                  packages.end(), 
32                                                  currentBoxSize) - packages.begin();
33              
34                // All packages from packageIndex to nextPackageIndex-1 will use this box size
35                totalBoxSpace += 1LL * (nextPackageIndex - packageIndex) * currentBoxSize;
36              
37                // Move to next unassigned package
38                packageIndex = nextPackageIndex;
39            }
40          
41            // Update minimum total box space needed
42            minTotalBoxSpace = min(minTotalBoxSpace, totalBoxSpace);
43        }
44      
45        // If no supplier can accommodate all packages, return -1
46        if (minTotalBoxSpace == INF) {
47            return -1;
48        }
49      
50        // Calculate total package space
51        long long totalPackageSpace = accumulate(packages.begin(), packages.end(), 0LL);
52      
53        // Return wasted space (total box space - total package space) modulo MOD
54        return (minTotalBoxSpace - totalPackageSpace) % MOD;
55    }
56};
57
1/**
2 * Finds the minimum wasted space when packing packages into boxes
3 * @param packages - Array of package sizes
4 * @param boxes - 2D array where each row represents available box sizes from a supplier
5 * @returns Minimum wasted space modulo 1000000007, or -1 if impossible
6 */
7function minWastedSpace(packages: number[], boxes: number[][]): number {
8    const MOD = 1000000007;
9    const packageCount = packages.length;
10    const INFINITY = Infinity;
11  
12    // Sort packages in ascending order for binary search
13    packages.sort((a, b) => a - b);
14  
15    let minTotalSpace = INFINITY;
16    const largestPackage = packages[packageCount - 1];
17  
18    // Try each supplier's box options
19    for (const supplierBoxes of boxes) {
20        // Sort current supplier's boxes in ascending order
21        supplierBoxes.sort((a, b) => a - b);
22      
23        // Skip this supplier if their largest box can't fit the largest package
24        const largestBox = supplierBoxes[supplierBoxes.length - 1];
25        if (largestPackage > largestBox) {
26            continue;
27        }
28      
29        // Calculate total space used with this supplier's boxes
30        let totalSpaceUsed = 0;
31        let currentPackageIndex = 0;
32      
33        // For each box size, find how many packages it will contain
34        for (const boxSize of supplierBoxes) {
35            // Find the index of first package that doesn't fit in current box
36            const nextPackageIndex = binarySearchUpperBound(packages, boxSize, currentPackageIndex);
37          
38            // Calculate space: number of packages * box size
39            const packagesInThisBox = nextPackageIndex - currentPackageIndex;
40            totalSpaceUsed += packagesInThisBox * boxSize;
41          
42            currentPackageIndex = nextPackageIndex;
43        }
44      
45        minTotalSpace = Math.min(minTotalSpace, totalSpaceUsed);
46    }
47  
48    // Check if any valid packing was found
49    if (minTotalSpace === INFINITY) {
50        return -1;
51    }
52  
53    // Calculate total package size
54    const totalPackageSize = packages.reduce((sum, packageSize) => sum + packageSize, 0);
55  
56    // Return wasted space (total space - package size) modulo MOD
57    return (minTotalSpace - totalPackageSize) % MOD;
58}
59
60/**
61 * Binary search to find the first index where nums[index] > target
62 * @param nums - Sorted array to search in
63 * @param target - Target value to compare against
64 * @param startIndex - Starting index for the search
65 * @returns Index of first element greater than target, or array length if none found
66 */
67function search(nums: number[], target: number, startIndex: number): number {
68    let left = startIndex;
69    let right = nums.length;
70  
71    while (left < right) {
72        const mid = (left + right) >> 1;  // Equivalent to Math.floor((left + right) / 2)
73      
74        if (nums[mid] > target) {
75            // Target is in left half
76            right = mid;
77        } else {
78            // Target is in right half or equal to mid
79            left = mid + 1;
80        }
81    }
82  
83    return left;
84}
85
86// Alias for better naming clarity
87const binarySearchUpperBound = search;
88

Time and Space Complexity

Time Complexity: O(n log n + m * k log k + m * n log n) where:

  • n is the length of the packages array
  • m is the number of suppliers (length of boxes array)
  • k is the average number of boxes per supplier

Breaking down the operations:

  • Sorting packages: O(n log n)
  • For each supplier box array (m iterations):
    • Sorting each box array: O(k log k)
    • For each box size in the sorted array (k iterations):
      • Binary search using bisect_right: O(log n)
    • Total per supplier: O(k log k + k * log n)
  • Overall: O(n log n + m * (k log k + k * log n))

In the worst case where k ≈ n, this becomes O(n log n + m * n log n) = O((m + 1) * n log n)

Space Complexity: O(1) or O(log n) depending on the sorting algorithm implementation

  • The algorithm sorts arrays in-place
  • Variables ans, s, i, j, mod use constant space
  • The sorting operation may use O(log n) space for the recursion stack in languages like Python (Timsort)
  • No additional data structures are created

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

Common Pitfalls

1. Integer Overflow When Calculating Total Space

The most critical pitfall in this solution is that total_space_used can exceed Python's integer limits before applying the modulo operation. When you have many packages and large box sizes, the multiplication packages_count * box_size and the accumulation in total_space_used can produce extremely large numbers.

Why this happens:

  • Constraints typically allow up to 10^5 packages and box sizes up to 10^9
  • The total space calculation accumulates these products: total_space_used += packages_count * box_size
  • This can result in values around 10^14 or larger before taking modulo

The problem:

# Current problematic approach
total_space_used += packages_count * box_size  # Can overflow
# ...later...
wasted_space = (min_wasted_space - total_package_size) % MOD  # Modulo applied too late

Solution: Apply modulo during accumulation to keep numbers manageable:

# Corrected approach
total_space_used = (total_space_used + packages_count * box_size) % MOD

However, this creates another issue: you can't directly compare modulo values for finding the minimum. The complete solution requires tracking both the actual minimum and applying modulo only at the final return.

2. Incorrect Minimum Comparison After Modulo

If you apply modulo during calculation, comparing suppliers becomes incorrect:

# Wrong: Can't find true minimum after modulo
for box_sizes in boxes:
    total_space_used = 0
    # ... calculate with modulo ...
    total_space_used = (total_space_used + packages_count * box_size) % MOD
    min_wasted_space = min(min_wasted_space, total_space_used)  # Incorrect comparison!

Correct Solution:

class Solution:
    def minWastedSpace(self, packages: List[int], boxes: List[List[int]]) -> int:
        MOD = 10**9 + 7
        min_wasted_space = inf
      
        packages.sort()
        total_package_size = sum(packages)
      
        for box_sizes in boxes:
            box_sizes.sort()
          
            if packages[-1] > box_sizes[-1]:
                continue
          
            # Keep actual value for comparison
            total_space_used = 0
            package_index = 0
          
            for box_size in box_sizes:
                next_package_index = bisect_right(packages, box_size, lo=package_index)
                packages_count = next_package_index - package_index
                total_space_used += packages_count * box_size
                package_index = next_package_index
          
            # Compare actual values
            min_wasted_space = min(min_wasted_space, total_space_used)
      
        if min_wasted_space == inf:
            return -1
      
        # Apply modulo only at the end
        wasted_space = (min_wasted_space - total_package_size) % MOD
        return wasted_space

3. Not Handling the Case Where All Packages Are Already Processed

A subtle issue occurs when iterating through box sizes if all packages have already been assigned:

# Potential inefficiency
for box_size in box_sizes:
    next_package_index = bisect_right(packages, box_size, lo=package_index)
    # If package_index == len(packages), this still runs but does nothing

Optimization:

for box_size in box_sizes:
    if package_index >= len(packages):
        break  # All packages already assigned
    next_package_index = bisect_right(packages, box_size, lo=package_index)
    # ... rest of the logic

This early termination can improve performance when a supplier has many more box sizes than necessary.

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

Which type of traversal does breadth first search do?


Recommended Readings

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

Load More