1580. Put Boxes Into the Warehouse II π
Problem Description
You have two arrays: boxes
containing heights of boxes (all unit width) and warehouse
containing heights of n
rooms in a warehouse. The warehouse rooms are numbered from 0
to n-1
from left to right, where warehouse[i]
represents the height of room i
.
The rules for placing boxes are:
- Boxes cannot be stacked on top of each other
- You can rearrange the boxes in any order you want
- You can push boxes into the warehouse from either the left side or the right side
- When pushing a box through the warehouse, if it encounters a room with height less than the box's height, the box gets stuck there and cannot go further. All subsequent boxes pushed from the same side will also be blocked
Your goal is to find the maximum number of boxes that can be placed in the warehouse.
For example, if you have a warehouse with room heights [4, 3, 5, 2, 6]
and you're pushing a box of height 4
from the left, it will stop at room index 1
(height 3
). If you push the same box from the right, it will stop at room index 3
(height 2
).
The solution involves preprocessing the warehouse to determine the effective height constraint for each room (considering boxes can come from either direction), then using a greedy approach by sorting both arrays and matching smaller boxes to available rooms.
Intuition
The key insight is understanding what happens when we push boxes from either side. When a box is pushed from the left and gets stuck at position i
, it means the box couldn't pass through some room before i
that was too short. Similarly, when pushing from the right.
For any room at position i
, we need to figure out: what's the tallest box that could actually reach this room? This depends on the minimum height of all rooms the box must pass through to get there.
If we push from the left to reach room i
, the box must pass through rooms 0, 1, ..., i-1
. So the constraint is min(warehouse[0], warehouse[1], ..., warehouse[i-1])
.
If we push from the right to reach room i
, the box must pass through rooms n-1, n-2, ..., i+1
. So the constraint is min(warehouse[i+1], warehouse[i+2], ..., warehouse[n-1])
.
Since we can choose which side to push from, the effective height limit for room i
is the better of these two options: max(left_constraint, right_constraint)
. But we also can't exceed the room's own height, so the final constraint is min(warehouse[i], max(left_constraint, right_constraint))
.
Once we know the effective height constraint for each room, the problem becomes simpler: we have a list of room capacities and a list of box heights, and we want to match as many boxes as possible to rooms.
The greedy approach works here: sort both arrays and try to place the smallest boxes first. For each box, find the smallest room that can accommodate it. This maximizes our chances of fitting more boxes because we're using the "cheapest" resources (smallest acceptable rooms) for each box, leaving larger rooms available for larger boxes.
Solution Approach
The implementation consists of three main phases: preprocessing, sorting, and greedy matching.
Phase 1: Preprocessing the Warehouse
We create two auxiliary arrays left
and right
to track the height constraints:
left[i]
stores the minimum height among all rooms to the left of positioni
(rooms that a box must pass through when pushed from the left)right[i]
stores the minimum height among all rooms to the right of positioni
(rooms that a box must pass through when pushed from the right)
left[0] = right[-1] = inf # No constraints at the boundaries
for i in range(1, n):
left[i] = min(left[i - 1], warehouse[i - 1])
for i in range(n - 2, -1, -1):
right[i] = min(right[i + 1], warehouse[i + 1])
Then we update each room's effective height based on the better access path:
for i in range(n):
warehouse[i] = min(warehouse[i], max(left[i], right[i]))
This gives us the maximum box height that can actually be placed in each room, considering access from either direction.
Phase 2: Sorting
Both arrays are sorted in ascending order:
boxes.sort() warehouse.sort()
Phase 3: Greedy Matching
We use two pointers to match boxes to rooms:
ans = i = 0 for x in boxes: while i < n and warehouse[i] < x: i += 1 if i == n: break ans, i = ans + 1, i + 1
For each box (starting from smallest), we find the first available room that can accommodate it. If found, we place the box and move to the next room. If we run out of rooms, we stop.
The time complexity is O(n log n + m log m)
for sorting, where n
is the warehouse size and m
is the number of boxes. The space complexity is O(n)
for the auxiliary arrays.
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 warehouse = [4, 3, 5, 2, 6]
and boxes = [3, 5, 5, 2]
.
Step 1: Preprocessing the Warehouse
First, we build the left
and right
arrays to track minimum heights:
left[i]
= minimum height of rooms to the left of position iright[i]
= minimum height of rooms to the right of position i
Building left
array (rooms that must be passed when pushing from left):
left[0] = inf
(no rooms to the left)left[1] = min(inf, warehouse[0]) = min(inf, 4) = 4
left[2] = min(4, warehouse[1]) = min(4, 3) = 3
left[3] = min(3, warehouse[2]) = min(3, 5) = 3
left[4] = min(3, warehouse[3]) = min(3, 2) = 2
Building right
array (rooms that must be passed when pushing from right):
right[4] = inf
(no rooms to the right)right[3] = min(inf, warehouse[4]) = min(inf, 6) = 6
right[2] = min(6, warehouse[3]) = min(6, 2) = 2
right[1] = min(2, warehouse[2]) = min(2, 5) = 2
right[0] = min(2, warehouse[1]) = min(2, 3) = 2
Now we update each room's effective height:
- Room 0:
min(4, max(left[0], right[0])) = min(4, max(inf, 2)) = min(4, inf) = 4
- Room 1:
min(3, max(left[1], right[1])) = min(3, max(4, 2)) = min(3, 4) = 3
- Room 2:
min(5, max(left[2], right[2])) = min(5, max(3, 2)) = min(5, 3) = 3
- Room 3:
min(2, max(left[3], right[3])) = min(2, max(3, 6)) = min(2, 6) = 2
- Room 4:
min(6, max(left[4], right[4])) = min(6, max(2, inf)) = min(6, inf) = 6
Updated warehouse = [4, 3, 3, 2, 6]
Step 2: Sorting
- Sort
boxes
:[2, 3, 5, 5]
- Sort
warehouse
:[2, 3, 3, 4, 6]
Step 3: Greedy Matching
Using two pointers, match boxes to rooms:
-
Box height 2, room pointer at index 0:
- Room height 2 β₯ box height 2 β
- Place box, increment both pointers
- Count = 1
-
Box height 3, room pointer at index 1:
- Room height 3 β₯ box height 3 β
- Place box, increment both pointers
- Count = 2
-
Box height 5, room pointer at index 2:
- Room height 3 < box height 5, skip room
- Room height 4 < box height 5, skip room
- Room height 6 β₯ box height 5 β
- Place box, increment both pointers
- Count = 3
-
Box height 5, room pointer at index 5:
- No more rooms available
- Stop
Result: Maximum 3 boxes can be placed in the warehouse.
Solution Implementation
1from typing import List
2from math import inf
3
4class Solution:
5 def maxBoxesInWarehouse(self, boxes: List[int], warehouse: List[int]) -> int:
6 # Get the length of warehouse
7 warehouse_length = len(warehouse)
8
9 # Initialize arrays to track minimum heights from left and right
10 min_height_from_left = [0] * warehouse_length
11 min_height_from_right = [0] * warehouse_length
12
13 # Set boundaries to infinity (no constraint from outside)
14 min_height_from_left[0] = inf
15 min_height_from_right[-1] = inf
16
17 # Calculate minimum height encountered when entering from left
18 for i in range(1, warehouse_length):
19 min_height_from_left[i] = min(min_height_from_left[i - 1], warehouse[i - 1])
20
21 # Calculate minimum height encountered when entering from right
22 for i in range(warehouse_length - 2, -1, -1):
23 min_height_from_right[i] = min(min_height_from_right[i + 1], warehouse[i + 1])
24
25 # Update each warehouse position with effective height
26 # (considering boxes can enter from either side)
27 for i in range(warehouse_length):
28 warehouse[i] = min(warehouse[i], max(min_height_from_left[i], min_height_from_right[i]))
29
30 # Sort both arrays for greedy matching
31 boxes.sort()
32 warehouse.sort()
33
34 # Greedy approach: match smallest boxes with smallest available spaces
35 placed_boxes = 0
36 warehouse_index = 0
37
38 for box_height in boxes:
39 # Skip warehouse positions that cannot fit current box
40 while warehouse_index < warehouse_length and warehouse[warehouse_index] < box_height:
41 warehouse_index += 1
42
43 # If no more warehouse positions available, stop
44 if warehouse_index == warehouse_length:
45 break
46
47 # Place the box and move to next warehouse position
48 placed_boxes += 1
49 warehouse_index += 1
50
51 return placed_boxes
52
1class Solution {
2 public int maxBoxesInWarehouse(int[] boxes, int[] warehouse) {
3 int warehouseLength = warehouse.length;
4
5 // Arrays to track minimum heights when approaching from left and right
6 int[] minHeightFromLeft = new int[warehouseLength];
7 int[] minHeightFromRight = new int[warehouseLength];
8
9 // Use a large value to represent no constraint
10 final int INFINITY = 1 << 30;
11
12 // Initialize boundaries - no constraint at the entrances
13 minHeightFromLeft[0] = INFINITY;
14 minHeightFromRight[warehouseLength - 1] = INFINITY;
15
16 // Calculate minimum height constraints when pushing from the left
17 // Each position's constraint is the minimum of all previous warehouse heights
18 for (int i = 1; i < warehouseLength; ++i) {
19 minHeightFromLeft[i] = Math.min(minHeightFromLeft[i - 1], warehouse[i - 1]);
20 }
21
22 // Calculate minimum height constraints when pushing from the right
23 // Each position's constraint is the minimum of all subsequent warehouse heights
24 for (int i = warehouseLength - 2; i >= 0; --i) {
25 minHeightFromRight[i] = Math.min(minHeightFromRight[i + 1], warehouse[i + 1]);
26 }
27
28 // Update each warehouse position with its effective height
29 // A box can reach position i from either direction, so take the better option
30 for (int i = 0; i < warehouseLength; ++i) {
31 warehouse[i] = Math.min(warehouse[i], Math.max(minHeightFromLeft[i], minHeightFromRight[i]));
32 }
33
34 // Sort both arrays to use greedy matching strategy
35 Arrays.sort(boxes);
36 Arrays.sort(warehouse);
37
38 // Greedy matching: try to fit smallest boxes into smallest available spaces
39 int boxCount = 0;
40 int warehouseIndex = 0;
41
42 for (int boxHeight : boxes) {
43 // Find the first warehouse position that can accommodate this box
44 while (warehouseIndex < warehouseLength && warehouse[warehouseIndex] < boxHeight) {
45 ++warehouseIndex;
46 }
47
48 // If no suitable position found, we can't place any more boxes
49 if (warehouseIndex == warehouseLength) {
50 break;
51 }
52
53 // Place the box and move to next warehouse position
54 ++boxCount;
55 ++warehouseIndex;
56 }
57
58 return boxCount;
59 }
60}
61
1class Solution {
2public:
3 int maxBoxesInWarehouse(vector<int>& boxes, vector<int>& warehouse) {
4 int warehouseSize = warehouse.size();
5 const int INFINITY = 1 << 30;
6
7 // Track minimum height constraints from left and right directions
8 vector<int> minHeightFromLeft(warehouseSize, INFINITY);
9 vector<int> minHeightFromRight(warehouseSize, INFINITY);
10
11 // Calculate minimum height when pushing from left
12 // Each position's constraint is the minimum of all previous positions
13 for (int i = 1; i < warehouseSize; ++i) {
14 minHeightFromLeft[i] = min(minHeightFromLeft[i - 1], warehouse[i - 1]);
15 }
16
17 // Calculate minimum height when pushing from right
18 // Each position's constraint is the minimum of all following positions
19 for (int i = warehouseSize - 2; i >= 0; --i) {
20 minHeightFromRight[i] = min(minHeightFromRight[i + 1], warehouse[i + 1]);
21 }
22
23 // Update each warehouse position with effective height
24 // A box can reach position i from either left or right entrance
25 // Take the better (larger) of the two minimum paths
26 for (int i = 0; i < warehouseSize; ++i) {
27 warehouse[i] = min(warehouse[i], max(minHeightFromLeft[i], minHeightFromRight[i]));
28 }
29
30 // Sort both arrays for greedy matching
31 sort(boxes.begin(), boxes.end());
32 sort(warehouse.begin(), warehouse.end());
33
34 // Greedy matching: assign smallest boxes to smallest available warehouse positions
35 int placedBoxes = 0;
36 int warehouseIndex = 0;
37
38 for (int boxHeight : boxes) {
39 // Skip warehouse positions that cannot accommodate current box
40 while (warehouseIndex < warehouseSize && warehouse[warehouseIndex] < boxHeight) {
41 ++warehouseIndex;
42 }
43
44 // No more warehouse positions available
45 if (warehouseIndex == warehouseSize) {
46 break;
47 }
48
49 // Place box in current warehouse position
50 ++placedBoxes;
51 ++warehouseIndex;
52 }
53
54 return placedBoxes;
55 }
56};
57
1function maxBoxesInWarehouse(boxes: number[], warehouse: number[]): number {
2 const warehouseSize = warehouse.length;
3 const INFINITY = 1 << 30;
4
5 // Track minimum height constraints from left and right directions
6 const minHeightFromLeft: number[] = new Array(warehouseSize).fill(INFINITY);
7 const minHeightFromRight: number[] = new Array(warehouseSize).fill(INFINITY);
8
9 // Calculate minimum height when pushing from left
10 // Each position's constraint is the minimum of all previous positions
11 for (let i = 1; i < warehouseSize; i++) {
12 minHeightFromLeft[i] = Math.min(minHeightFromLeft[i - 1], warehouse[i - 1]);
13 }
14
15 // Calculate minimum height when pushing from right
16 // Each position's constraint is the minimum of all following positions
17 for (let i = warehouseSize - 2; i >= 0; i--) {
18 minHeightFromRight[i] = Math.min(minHeightFromRight[i + 1], warehouse[i + 1]);
19 }
20
21 // Update each warehouse position with effective height
22 // A box can reach position i from either left or right entrance
23 // Take the better (larger) of the two minimum paths
24 for (let i = 0; i < warehouseSize; i++) {
25 warehouse[i] = Math.min(warehouse[i], Math.max(minHeightFromLeft[i], minHeightFromRight[i]));
26 }
27
28 // Sort both arrays for greedy matching
29 boxes.sort((a, b) => a - b);
30 warehouse.sort((a, b) => a - b);
31
32 // Greedy matching: assign smallest boxes to smallest available warehouse positions
33 let placedBoxes = 0;
34 let warehouseIndex = 0;
35
36 for (const boxHeight of boxes) {
37 // Skip warehouse positions that cannot accommodate current box
38 while (warehouseIndex < warehouseSize && warehouse[warehouseIndex] < boxHeight) {
39 warehouseIndex++;
40 }
41
42 // No more warehouse positions available
43 if (warehouseIndex === warehouseSize) {
44 break;
45 }
46
47 // Place box in current warehouse position
48 placedBoxes++;
49 warehouseIndex++;
50 }
51
52 return placedBoxes;
53}
54
Time and Space Complexity
The time complexity of this code is O(n + m log m)
, where n
is the length of the warehouse array and m
is the length of the boxes array.
Breaking down the time complexity:
- The first loop to build the
left
array:O(n)
- The second loop to build the
right
array:O(n)
- The third loop to update warehouse values:
O(n)
- Sorting the boxes array:
O(m log m)
- Sorting the warehouse array:
O(n log n)
- The final loop to match boxes with warehouse positions:
O(m + n)
in the worst case
The dominant operations are the sorting steps, giving us O(n log n + m log m)
. However, if we consider that typically m
might be different from n
, the more precise complexity would be O(n log n + m log m)
. When the reference answer states O(n log n)
, it appears to be considering the case where the warehouse sorting dominates, or potentially treating both arrays as having similar sizes.
The space complexity is O(n)
, where n
is the length of the warehouse array.
Breaking down the space complexity:
- The
left
array:O(n)
- The
right
array:O(n)
- The sorting operations use
O(log n)
andO(log m)
for the recursive stack space (assuming Python's Timsort)
The total space complexity is O(n)
as the auxiliary arrays dominate the space usage.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Misunderstanding the Warehouse Update Logic
A common mistake is incorrectly calculating the effective height for each warehouse position. Developers often write:
Incorrect:
warehouse[i] = max(left[i], right[i]) # Wrong!
This would mean a box needs to be tall enough for BOTH paths, when actually we want the BETTER of the two paths.
Correct:
warehouse[i] = min(warehouse[i], max(left[i], right[i]))
The logic is: we take the maximum of the two path constraints (choosing the better path), then ensure it doesn't exceed the room's actual height.
Pitfall 2: Off-by-One Errors in Preprocessing
Another frequent error occurs when building the left
and right
arrays with incorrect index boundaries:
Incorrect:
# Wrong boundary conditions
for i in range(n): # Should start from 1
left[i] = min(left[i - 1], warehouse[i - 1]) # Would cause index error
for i in range(n - 1, -1, -1): # Should start from n-2
right[i] = min(right[i + 1], warehouse[i + 1]) # Would cause index error
Correct:
for i in range(1, n): # Start from 1 since left[0] is already set
left[i] = min(left[i - 1], warehouse[i - 1])
for i in range(n - 2, -1, -1): # Start from n-2 since right[n-1] is already set
right[i] = min(right[i + 1], warehouse[i + 1])
Pitfall 3: Modifying Original Input Unnecessarily
If the problem requires preserving the original warehouse array, directly modifying it would be incorrect:
Better Practice:
# Create a copy if original needs to be preserved effective_heights = warehouse.copy() # Then modify effective_heights instead of warehouse
Pitfall 4: Forgetting Edge Cases
Not handling edge cases like empty arrays:
Add validation:
if not boxes or not warehouse: return 0
These pitfalls can lead to incorrect results or runtime errors, so careful attention to the preprocessing logic and boundary conditions is essential.
Which algorithm is best for finding the shortest distance between two points in an unweighted graph?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
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
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Donβt Miss This!