Maximum Height by Stacking Cuboids
HardPrompt
Given n cuboids where the dimensions of the ith cuboid is cuboids[i] = [widthi, lengthi, heighti] (0-indexed). Choose a subset of cuboids and place them on each other.
You can place cuboid i on cuboid j if widthi <= widthj and lengthi <= lengthj and heighti <= heightj. You can rearrange any cuboid's dimensions by rotating it to put it on another cuboid.
Return the maximum height of the stacked cuboids.
Example 1:

Input: cuboids = [[50,45,20],[95,37,53],[45,23,12]]
Output: 190
Explanation:
Cuboid 1 is placed on the bottom with the 53x37 side facing down with height 95.
Cuboid 0 is placed next with the 45x20 side facing down with height 50.
Cuboid 2 is placed next with the 23x12 side facing down with height 45.
The total height is 95 + 50 + 45 = 190.Example 2:
Input: cuboids = [[38,25,45],[76,35,3]]
Output: 76
Explanation:
You can't place any of the cuboids on the other.
We choose cuboid 1 and rotate it so that the 35x3 side is facing down and its height is 76.Example 3:
Input: cuboids = [[7,11,17],[7,17,11],[11,7,17],[11,17,7],[17,7,11],[17,11,7]]
Output: 102
Explanation:
After rearranging the cuboids, you can see that all cuboids have the same dimension.
You can place the 11x7 side down on all cuboids so their heights are 17.
The maximum height of stacked cuboids is 6 * 17 = 102.
Constraints:
n == cuboids.length1 <= n <= 1001 <= widthi, lengthi, heighti <= 100
Approaches
2 approaches with complexity analysis and trade-offs.
This approach guarantees finding the optimal solution by considering all possible valid orientations for each cuboid. The core idea is to transform the problem into a variation of the Longest Increasing Subsequence (LIS) problem. We generate every possible way a cuboid can be oriented, creating a larger list of potential building blocks for our stack. Then, we use dynamic programming to find the sequence of these blocks that can be stacked on top of each other and results in the maximum total height.
Algorithm
- For each of the
ncuboids, generate all 3 possible unique orientations. To make comparisons standard, for each orientation, represent its base dimensions(width, length)such thatwidth <= length. - This results in a list of up to
3ncandidate cuboids. Each candidate should store its dimensions(w, l, h)and the index of the original cuboid it came from. - Sort this list of
3ncandidates. A good sorting order is lexicographically by(w, l, h). - Initialize a dynamic programming array,
dp, of size3n.dp[i]will store the maximum height of a stack that has candidateiat the very top. - Iterate through the sorted candidates from
i = 0to3n-1:- Initialize
dp[i]to the height of candidatei,h_i. This represents a stack consisting of only this single cuboid. - Iterate through all previous candidates
jfrom0toi-1. - Check if candidate
jcan be placed under candidatei. This requires two conditions:- They must originate from different cuboids:
original_index_j != original_index_i. - The dimensions must be non-decreasing:
w_j <= w_i,l_j <= l_i, andh_j <= h_i.
- They must originate from different cuboids:
- If candidate
jcan be placed underi, it means we can potentially form a taller stack. Updatedp[i] = max(dp[i], h_i + dp[j]).
- Initialize
- The final answer is the maximum value found in the
dparray, which represents the maximum height achievable across all possible valid stacks.
Walkthrough
To solve this problem correctly, we must account for the fact that any cuboid can be rotated. A cuboid with dimensions (d1, d2, d3) can be oriented in three distinct ways by choosing each dimension as the height. For consistency, we can order the base dimensions (width and length). This gives us three candidate orientations for each cuboid:
- Height
d1, Base(min(d2, d3), max(d2, d3)). Cuboid:[min(d2, d3), max(d2, d3), d1] - Height
d2, Base(min(d1, d3), max(d1, d3)). Cuboid:[min(d1, d3), max(d1, d3), d2] - Height
d3, Base(min(d1, d2), max(d1, d2)). Cuboid:[min(d1, d2), max(d1, d2), d3]
We generate these three orientations for each of the n input cuboids, creating a list of 3n candidates. The problem then becomes finding the highest stack using a subset of these candidates, with the constraint that we can only use one orientation from each original cuboid.
A crucial insight is that if we have a valid stack, we cannot use two different orientations of the same original cuboid. This is because for one orientation o1 to be stacked on another o2 from the same original cuboid, their dimensions (w, l, h) must be non-decreasing. Since they are permutations of the same set of dimensions, this is only possible if they are identical, which would violate the 'subset' rule if we disallow stacking identical items from the same source.
This simplifies the problem to finding the longest path in a Directed Acyclic Graph (DAG), where nodes are the 3n candidates and an edge exists from j to i if candidate j can be placed under i. The weight of the path is the sum of heights. This is a classic LIS-type problem solvable with dynamic programming.
import java.util.Arrays;import java.util.ArrayList;import java.util.Collections; class Solution { // A helper class to store candidate cuboid orientations static class CuboidCandidate { int w, l, h, originalIndex; public CuboidCandidate(int w, int l, int h, int originalIndex) { this.w = w; this.l = l; this.h = h; this.originalIndex = originalIndex; } } public int maxHeight(int[][] cuboids) { ArrayList<CuboidCandidate> candidates = new ArrayList<>(); for (int i = 0; i < cuboids.length; i++) { int[] c = cuboids[i]; // Orientation 1 candidates.add(new CuboidCandidate(Math.min(c[0], c[1]), Math.max(c[0], c[1]), c[2], i)); // Orientation 2 candidates.add(new CuboidCandidate(Math.min(c[0], c[2]), Math.max(c[0], c[2]), c[1], i)); // Orientation 3 candidates.add(new CuboidCandidate(Math.min(c[1], c[2]), Math.max(c[1], c[2]), c[0], i)); } // Sort candidates to enable DP Collections.sort(candidates, (a, b) -> { if (a.w != b.w) return Integer.compare(a.w, b.w); if (a.l != b.l) return Integer.compare(a.l, b.l); return Integer.compare(a.h, b.h); }); int n = candidates.size(); int[] dp = new int[n]; int maxHeight = 0; for (int i = 0; i < n; i++) { dp[i] = candidates.get(i).h; // Base case: stack of one for (int j = 0; j < i; j++) { CuboidCandidate current = candidates.get(i); CuboidCandidate prev = candidates.get(j); // Check if we can stack current on top of prev if (prev.originalIndex != current.originalIndex && prev.w <= current.w && prev.l <= current.l && prev.h <= current.h) { dp[i] = Math.max(dp[i], current.h + dp[j]); } } maxHeight = Math.max(maxHeight, dp[i]); } return maxHeight; }}Complexity
Time
O(n^2). Generating candidates takes O(n). Sorting `3n` candidates takes O(n log n). The nested loops for the dynamic programming part take O((3n)^2) = O(n^2). Thus, the overall time complexity is dominated by the DP calculation.
Space
O(n), where n is the number of cuboids. We store `3n` candidates and a `dp` array of size `3n`, which simplifies to O(n).
Trade-offs
Pros
This approach is guaranteed to be correct as it explores all valid stacking configurations.
It correctly models the problem as a longest path in a DAG, which has a standard DP solution.
Cons
The state space for the DP is larger (
3ninstead ofn), leading to a higher constant factor in time complexity compared to simpler heuristics.The implementation is more complex as it requires generating and managing orientations for each cuboid.
A simpler and faster, but potentially incorrect, approach is to use a greedy heuristic. The heuristic is to decide on a fixed orientation for each cuboid before attempting to stack them. A natural greedy choice is to orient each cuboid such that its largest dimension is its height, as this maximizes the height contribution of each individual cuboid. After fixing the orientation for all cuboids, the problem reduces to finding the longest increasing subsequence on these n fixed cuboids, which can be solved with dynamic programming.
Algorithm
- For each cuboid, create a canonical representation by sorting its dimensions, e.g.,
[d1, d2, d3]becomes[w, l, h]wherew <= l <= h. This greedily assumes the largest dimension should always be the height. - You now have a list of
ncanonical cuboids. - Sort this list of
ncuboids, for example, lexicographically based on their(w, l, h)dimensions. - Initialize a
dparray of sizen, wheredp[i]stores the maximum height of a stack ending with cuboidi. - Iterate from
i = 0ton-1:- Set
dp[i] = h_ias the base case. - Iterate from
j = 0toi-1. - If cuboid
jcan be placed under cuboidi(i.e.,w_j <= w_i,l_j <= l_i,h_j <= h_i), updatedp[i] = max(dp[i], h_i + dp[j]).
- Set
- The answer is the maximum value in the
dparray.
Walkthrough
This method simplifies the problem by making a greedy choice upfront. For each cuboid, we sort its three dimensions d1 <= d2 <= d3. We then treat this as a fixed cuboid with width d1, length d2, and height d3. This assumes that to maximize the total height, we should always use the largest possible dimension of each cuboid as its height.
Once we have this list of n 'canonical' cuboids, we sort them. Then, we apply a standard dynamic programming algorithm similar to finding the Longest Increasing Subsequence. Let dp[i] be the maximum height of a stack ending with cuboid i. We compute dp[i] by trying to place it on top of any preceding cuboid j that fits underneath it.
While this approach works for the given examples, it is not guaranteed to be correct. The initial greedy choice might prevent finding the true optimal solution. For instance, using a smaller height for a cuboid creates a larger base, which might be necessary to support a different, very tall cuboid that couldn't be placed otherwise.
import java.util.Arrays; class Solution { public int maxHeight(int[][] cuboids) { // Step 1: Create canonical representation for each cuboid for (int[] cuboid : cuboids) { Arrays.sort(cuboid); } // Step 2: Sort all cuboids Arrays.sort(cuboids, (a, b) -> { if (a[0] != b[0]) return Integer.compare(a[0], b[0]); if (a[1] != b[1]) return Integer.compare(a[1], b[1]); return Integer.compare(a[2], b[2]); }); // Step 3: Dynamic Programming (LIS) int n = cuboids.length; int[] dp = new int[n]; int maxHeight = 0; for (int i = 0; i < n; i++) { dp[i] = cuboids[i][2]; // Height of the current cuboid for (int j = 0; j < i; j++) { // Check if cuboid j can be placed under cuboid i if (cuboids[j][0] <= cuboids[i][0] && cuboids[j][1] <= cuboids[i][1] && cuboids[j][2] <= cuboids[i][2]) { dp[i] = Math.max(dp[i], cuboids[i][2] + dp[j]); } } maxHeight = Math.max(maxHeight, dp[i]); } return maxHeight; }}Complexity
Time
O(n^2). Sorting the `n` cuboids takes O(n log n). The DP calculation involves a nested loop, taking O(n^2) time. The total complexity is O(n^2).
Space
O(n) for the `dp` array.
Trade-offs
Pros
Simpler to understand and implement than the fully correct solution.
Faster in practice due to a smaller DP state space (
nvs3n), resulting in smaller constant factors in its time complexity.
Cons
The core greedy assumption is flawed. Always choosing the largest dimension as the height is not always optimal. A smaller height might allow a cuboid to be placed on a wider variety of other cuboids, or enable a larger cuboid to be placed on top of it, leading to a greater total height.
This approach will fail on certain test cases where the optimal solution requires using a smaller dimension as a height for some cuboids in the stack.
Solutions
Solution
class Solution {public int maxHeight(int[][] cuboids) { for (var c : cuboids) { Arrays.sort(c); } Arrays.sort(cuboids, (a, b)->a[0] == b[0] ? (a[1] == b[1] ? a[2] - b[2] : a[1] - b[1]) : a[0] - b[0]); int n = cuboids.length; int[] f = new int[n]; for (int i = 0; i < n; ++i) { for (int j = 0; j < i; ++j) { if (cuboids[j][1] <= cuboids[i][1] && cuboids[j][2] <= cuboids[i][2]) { f[i] = Math.max(f[i], f[j]); } } f[i] += cuboids[i][2]; } return Arrays.stream(f).max().getAsInt(); }}/** * @param {number[][]} cuboids * @return {number} */ var maxHeight = function (cuboids) { for (const c of cuboids) { c.sort((a, b) => a - b); } cuboids.sort((a, b) => { if (a[0] != b[0]) return a[0] - b[0]; if (a[1] != b[1]) return a[1] - b[1]; return a[2] - b[2]; }); const n = cuboids.length; const f = new Array(n).fill(0); for (let i = 0; i < n; ++i) { for (let j = 0; j < i; ++j) { const ok = cuboids[j][1] <= cuboids[i][1] && cuboids[j][2] <= cuboids[i][2]; if (ok) f[i] = Math.max(f[i], f[j]); } f[i] += cuboids[i][2]; } return Math.max(...f); };class Solution {public: int maxHeight(vector<vector<int>> &cuboids) { for (auto &c : cuboids) sort(c.begin(), c.end()); sort(cuboids.begin(), cuboids.end()); int n = cuboids.size(); vector<int> f(n); for (int i = 0; i < n; ++i) { for (int j = 0; j < i; ++j) { if (cuboids[j][1] <= cuboids[i][1] && cuboids[j][2] <= cuboids[i][2]) { f[i] = max(f[i], f[j]); } } f[i] += cuboids[i][2]; } return *max_element(f.begin(), f.end()); }};class Solution: def maxHeight(self, cuboids: List[List[int]]) -> int: for c in cuboids: c . sort() cuboids . sort() n = len(cuboids) f = [0] * n # f[i] meaning level-i as the base layer/level for i in range ( n ): for j in range ( i ): if cuboids [ j ][ 1 ] <= cuboids [ i ][ 1 ] and cuboids [ j ][ 2 ] <= cuboids [ i ][ 2 ]: f [ i ] = max ( f [ i ], f [ j ]) f [ i ] += cuboids [ i ][ 2 ] # not inside if block, eg input [[38,25,45],[76,35,3]] return max ( f ) ############ # in python3, range() is the same as xrange() in python2 class Solution : def maxHeight ( self , cuboids : List [ List [ int ]]) -> int : """ :type cuboids: List[List[int]] :rtype: int """ for cuboid in cuboids : cuboid . sort () cuboids . append ([ 0 , 0 , 0 ]) cuboids . sort () dp = [ 0 ] * len ( cuboids ) for i in range ( 1 , len ( cuboids )): for j in range ( i ): if all ( cuboids [ j ][ k ] <= cuboids [ i ][ k ] for k in range ( 3 )): dp [ i ] = max ( dp [ i ], dp [ j ] + cuboids [ i ][ 2 ]) return max ( dp )Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.