Problem overview
The method maximumUnits receives boxTypes, a list of pairs [numberOfBoxes, numberOfUnitsPerBox], and an integer truckSize, the most boxes the truck can carry. Each box takes one slot whatever it holds. The method returns the largest total number of units that fit on the truck.
In the first example, boxTypes = [[1, 3], [2, 2], [3, 1]] and truckSize = 4. Take the one box with 3 units, then the two boxes with 2 units, which makes 3 boxes and 7 units. One slot is left, and one box of the third type adds 1 more unit. The total is 8.
Approach: fill the truck with the densest boxes first
Every box uses exactly one of the truckSize slots, so the problem is about value per slot. A box with 9 units is always a better use of a slot than a box with 5. This suggests a greedy strategy: load boxes in order of units per box, from highest to lowest, until the truck is full or the boxes run out.
The strategy is correct because of an exchange argument. Suppose an answer uses a box with fewer units while a box with more units is left behind. Swapping the first box for the second keeps the box count the same and doesn't lower the total. So some best answer takes the densest boxes first, and the greedy loading finds it.
A brute-force search over every way to choose boxes grows exponentially, and even a smarter search is not needed here. Sorting plus one pass is enough.
In the second example, [[5, 10], [2, 5], [4, 7], [3, 9]] with truckSize = 10, sorting by units per box gives the order 10, 9, 7, 5. Take 5 boxes of 10 units (50), then 3 boxes of 9 units (27), which leaves 2 slots. Two boxes of 7 units add 14, and the total is 91.
Step by step
- Sort
boxTypesin place by units per box, largest first.list.sorttakes akeyfunction. Python sorts in ascending order of the key, so using the negated units per box,-x[1], puts the highest units first. - Start a running total in
units, set to 0. - Loop over the box types, unpacking each pair into
boxesandunits_per_box. - Decide how many boxes to take from this type:
take = min(boxes, truckSize). You can't load more boxes than the type has, nor more than the remaining space. - Add their units and use up the space. Add
take * units_per_boxtounits, and subtracttakefromtruckSize, which now means the free slots. - Stop when the truck is full. If
truckSize == 0,break. This is only a shortcut, because later iterations would take 0 boxes anyway. - Return
unitsafter the loop.
The solution in Python
class Solution:
def maximumUnits(self, boxTypes: List[List[int]], truckSize: int) -> int:
boxTypes.sort(key=lambda x: -x[1])
units = 0
for boxes, units_per_box in boxTypes:
take = min(boxes, truckSize)
units += take * units_per_box
truckSize -= take
if truckSize == 0:
break
return units
The Python details worth a closer look:
- The sort key.
lambda x: -x[1]is a small anonymous function. It receives one inner list, such as[2, 5], and returns the negated second item. An alternative that reads more directly isboxTypes.sort(key=lambda x: x[1], reverse=True). sort()works in place. It changes the caller's list and returnsNone. WritingboxTypes = boxTypes.sort(...)would replace the list withNone. To keep the original order, usesorted(boxTypes, key=...), which returns a new list.- Tuple unpacking in the loop header.
for boxes, units_per_box in boxTypesunpacks each two-item list into two names. It would fail if some entry did not have exactly two items. - Reusing a parameter.
truckSizeis a local name inside the method, so reassigning it with-=doesn't affect the caller's variable. Integers are immutable, sotruckSize -= takejust rebinds the name. - Large numbers need no care. Python integers don't overflow. The largest answer here is at most
10**6 * 1000, which would fit in most fixed-size types anyway.
Complexity
Time: O(n log n), where n is the number of box types. Sorting dominates. The loop that follows is O(n).
Space: O(n) in the worst case for the sort, because Python's sort may use a temporary buffer. The rest of the method uses a few variables.
Edge cases and common mistakes
- The truck is bigger than all the boxes.
[[1, 3]]withtruckSize = 10returns 3. The loop ends without the truck becoming full, and thebreaknever runs. - The truck fills in the middle of a type.
min(boxes, truckSize)takes just part of that type, which the first example does with the third type. - Sorting the wrong way. Without the negation, the loop takes the sparsest boxes first and returns a lower total.
- Sorting by number of boxes. The first item in each pair is the count, and the units per box is the second item,
x[1]. Sorting byx[0]orders the types by quantity, which doesn't help. - Multiplying the wrong numbers. The units are the number taken times units per box, not times the type's full box count.
- The input list changes. After the call, the caller's
boxTypesis sorted. If you need the original order, sort a copy.
Testing
boxTypes = [[1, 3], [2, 2], [3, 1]],truckSize = 4gives8.boxTypes = [[5, 10], [2, 5], [4, 7], [3, 9]],truckSize = 10gives91.boxTypes = [[1, 3]],truckSize = 10gives3.