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
wherepackages[i]
represents the size of thei-th
package - A 2D array
boxes
whereboxes[j]
contains all the different box sizes that supplierj
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
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:
- Haven't been assigned yet
- 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:
-
Initialize variables: Set
mod = 10^9 + 7
for the modulo operation, andans = inf
to track the minimum total space used across all suppliers. -
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. -
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
- Sort boxes:
-
Calculate total space for current supplier:
- Initialize
s = 0
(total space used) andi = 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 indexi
onwards can fit in box sizeb
- 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
- Use
- Initialize
-
Track minimum: Update
ans = min(ans, s)
to keep the minimum total space across all suppliers -
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
- If
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 EvaluatorExample 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
- Binary search: packages ≤ 10 are at indices [0,4) → packages
-
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
- Binary search: packages ≤ 15 from index 4 are at indices [4,6) → packages
-
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
- Binary search: packages ≤ 8 are at indices [0,3) → packages
-
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
- Binary search: packages ≤ 11 from index 3 are at indices [3,5) → packages
-
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
- Binary search: packages ≤ 12 from index 5 are at indices [5,6) → package
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 arraym
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)
- Binary search using
- Total per supplier:
O(k log k + k * log n)
- Sorting each box array:
- 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.
Which type of traversal does breadth first search do?
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
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Want a Structured Path to Master System Design Too? Don’t Miss This!