1105. Filling Bookcase Shelves
Problem Description
You have an array of books where each book is represented as books[i] = [thickness_i, height_i]
, indicating the thickness and height of the i-th
book. You also have a bookshelf with a fixed width shelfWidth
.
Your task is to place these books onto the bookshelf shelves following these rules:
- Books must be placed in the given order - you cannot rearrange the sequence of books
- For each shelf level:
- You can place consecutive books on the same shelf as long as the sum of their thicknesses doesn't exceed
shelfWidth
- The height added to the total bookshelf is the maximum height among all books on that shelf level
- You can place consecutive books on the same shelf as long as the sum of their thicknesses doesn't exceed
- Continue creating new shelf levels until all books are placed
For example, if you have 5 books in order and shelfWidth
allows:
- Books 1 and 2 might fit together on the first shelf (contributing the max height of these two books)
- Book 3 goes on the second shelf alone (contributing its height)
- Books 4 and 5 fit together on the third shelf (contributing the max height of these two books)
The goal is to find the minimum possible total height of the bookshelf after placing all books.
The dynamic programming solution works by:
f[i]
represents the minimum height needed to place the firsti
books- For each book position
i
, it considers:- Placing the book on a new shelf:
f[i] = f[i-1] + current_book_height
- Placing the book with previous books on the same shelf (if width permits): try grouping with
j-1, j, ..., i-1
books and take the minimum height configuration
- Placing the book on a new shelf:
Intuition
The key insight is that we need to make decisions about where to "break" the sequence of books into different shelves. Since books must be placed in order, this becomes a problem of finding optimal partition points.
Think of it this way: when we're about to place book i
, we have two choices:
- Start a new shelf with this book
- Try to fit it on the same shelf as some previous books
This naturally leads to a dynamic programming approach where we build up the solution for placing books 1
to i
based on solutions for smaller subproblems.
For each book position i
, we ask: "What's the minimum height if I've placed the first i
books?" To answer this, we need to determine which books should share the last shelf with book i
.
The clever part is working backwards from book i
. We can try putting book i
alone on a new shelf, or we can try combining it with book i-1
, or with books i-2
and i-1
, and so on - as long as the total thickness doesn't exceed shelfWidth
.
Why work backwards? Because we want to explore all valid configurations for the last shelf. Starting from book i
and looking back, we accumulate thickness and track the maximum height. Once the thickness exceeds shelfWidth
, we know we can't fit any more books on this shelf.
For each valid configuration of the last shelf (say books j
through i
), the total height would be:
- The minimum height to place books
1
throughj-1
(which we've already computed) - Plus the height of the current shelf (maximum height among books
j
throughi
)
We take the minimum across all these valid configurations. This ensures we find the optimal way to arrange books that minimizes the total height while respecting the ordering and width constraints.
Learn more about Dynamic Programming patterns.
Solution Approach
We implement a dynamic programming solution using a 1D array f
where f[i]
represents the minimum height needed to place the first i
books.
Initialization:
- Create array
f
of sizen + 1
wheren
is the number of books - Set
f[0] = 0
(no books means zero height)
Main Algorithm:
For each book i
(1-indexed), we iterate through all books and compute f[i]
:
-
Extract current book properties:
w = books[i-1][0]
(thickness)h = books[i-1][1]
(height)
-
Initial assumption: Place book
i
on a new shelff[i] = f[i-1] + h
-
Try combining with previous books:
- Iterate backwards from book
j = i-1
down toj = 1
- For each book
j
:- Add its thickness to current shelf:
w += books[j-1][0]
- If
w > shelfWidth
, break (can't fit more books) - Update shelf height:
h = max(h, books[j-1][1])
- Update minimum:
f[i] = min(f[i], f[j-1] + h)
- Add its thickness to current shelf:
- Iterate backwards from book
The backwards iteration logic:
When we consider placing books j
through i
on the same shelf:
f[j-1]
gives us the minimum height for books1
throughj-1
h
tracks the maximum height among booksj
throughi
- The total height would be
f[j-1] + h
Time Complexity: O(n²)
where n
is the number of books, as for each book we potentially check all previous books.
Space Complexity: O(n)
for the DP array.
The final answer is f[n]
, which represents the minimum height after optimally placing all n
books.
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 to illustrate the solution approach.
Input:
books = [[1,3], [2,4], [3,2]]
(format: [thickness, height])shelfWidth = 6
We need to find the minimum total height to place these 3 books in order.
Setup:
- Initialize DP array:
f = [0, 0, 0, 0]
f[0] = 0
(base case: no books = 0 height)
Processing Book 1 [1,3]:
- Current book: thickness=1, height=3
- Only option: place on new shelf
f[1] = f[0] + 3 = 0 + 3 = 3
- Result:
f = [0, 3, 0, 0]
Processing Book 2 [2,4]:
- Current book: thickness=2, height=4
- Option 1: Place on new shelf
f[2] = f[1] + 4 = 3 + 4 = 7
- Option 2: Combine with Book 1
- Combined thickness: 1 + 2 = 3 ≤ 6 ✓
- Shelf height: max(3, 4) = 4
f[2] = f[0] + 4 = 0 + 4 = 4
- Take minimum:
f[2] = min(7, 4) = 4
- Result:
f = [0, 3, 4, 0]
Processing Book 3 [3,2]:
- Current book: thickness=3, height=2
- Option 1: Place on new shelf
f[3] = f[2] + 2 = 4 + 2 = 6
- Option 2: Combine with Book 2
- Combined thickness: 2 + 3 = 5 ≤ 6 ✓
- Shelf height: max(4, 2) = 4
f[3] = f[1] + 4 = 3 + 4 = 7
- Option 3: Combine with Books 2 and 1
- Combined thickness: 1 + 2 + 3 = 6 ≤ 6 ✓
- Shelf height: max(3, 4, 2) = 4
f[3] = f[0] + 4 = 0 + 4 = 4
- Take minimum:
f[3] = min(6, 7, 4) = 4
- Result:
f = [0, 3, 4, 4]
Final Answer: f[3] = 4
The optimal arrangement places all three books on the same shelf (since their combined thickness is exactly 6), resulting in a total height of 4 (the maximum height among all books).
Solution Implementation
1class Solution:
2 def minHeightShelves(self, books: List[List[int]], shelfWidth: int) -> int:
3 # Number of books
4 n = len(books)
5
6 # dp[i] represents the minimum height needed to place the first i books
7 dp = [0] * (n + 1)
8
9 # Process each book
10 for i, (width, height) in enumerate(books, 1):
11 # Option 1: Place current book on a new shelf
12 dp[i] = dp[i - 1] + height
13
14 # Track cumulative width and max height for current shelf
15 cumulative_width = width
16 max_height = height
17
18 # Option 2: Try placing current book with previous books on same shelf
19 for j in range(i - 1, 0, -1):
20 # Add the width of the previous book
21 cumulative_width += books[j - 1][0]
22
23 # If shelf width is exceeded, stop trying
24 if cumulative_width > shelfWidth:
25 break
26
27 # Update the maximum height for this shelf
28 max_height = max(max_height, books[j - 1][1])
29
30 # Update dp[i] with minimum height between current and new arrangement
31 # dp[j - 1] + max_height means: height of first (j-1) books + height of current shelf
32 dp[i] = min(dp[i], dp[j - 1] + max_height)
33
34 # Return the minimum height needed for all n books
35 return dp[n]
36
1class Solution {
2 public int minHeightShelves(int[][] books, int shelfWidth) {
3 int n = books.length;
4 // dp[i] represents the minimum height to place the first i books
5 int[] dp = new int[n + 1];
6
7 // Process each book from 1 to n
8 for (int i = 1; i <= n; i++) {
9 // Get current book's width and height (converting to 0-indexed)
10 int currentWidth = books[i - 1][0];
11 int currentHeight = books[i - 1][1];
12
13 // Option 1: Place current book on a new shelf
14 dp[i] = dp[i - 1] + currentHeight;
15
16 // Option 2: Try to place current book with previous books on the same shelf
17 for (int j = i - 1; j > 0; j--) {
18 // Add the width of the previous book
19 currentWidth += books[j - 1][0];
20
21 // If total width exceeds shelf width, we can't fit more books
22 if (currentWidth > shelfWidth) {
23 break;
24 }
25
26 // Update the maximum height for this shelf
27 currentHeight = Math.max(currentHeight, books[j - 1][1]);
28
29 // Update dp[i] with the minimum height between current and new arrangement
30 // dp[j-1] is the height before placing books from j to i on the same shelf
31 dp[i] = Math.min(dp[i], dp[j - 1] + currentHeight);
32 }
33 }
34
35 // Return the minimum height to place all n books
36 return dp[n];
37 }
38}
39
1class Solution {
2public:
3 int minHeightShelves(vector<vector<int>>& books, int shelfWidth) {
4 int numBooks = books.size();
5
6 // dp[i] represents the minimum height of bookshelf to place first i books
7 vector<int> dp(numBooks + 1);
8 dp[0] = 0; // Base case: no books means zero height
9
10 // Iterate through each book position
11 for (int i = 1; i <= numBooks; ++i) {
12 // Current book dimensions
13 int currentWidth = books[i - 1][0];
14 int currentHeight = books[i - 1][1];
15
16 // Initialize: place current book on a new shelf
17 dp[i] = dp[i - 1] + currentHeight;
18
19 // Try placing previous books on the same shelf as current book
20 for (int j = i - 1; j > 0; --j) {
21 // Add width of book j to current shelf
22 currentWidth += books[j - 1][0];
23
24 // Check if shelf width constraint is violated
25 if (currentWidth > shelfWidth) {
26 break;
27 }
28
29 // Update maximum height of current shelf
30 currentHeight = max(currentHeight, books[j - 1][1]);
31
32 // Update minimum total height if placing books j to i on same shelf
33 dp[i] = min(dp[i], dp[j - 1] + currentHeight);
34 }
35 }
36
37 return dp[numBooks];
38 }
39};
40
1/**
2 * Calculates the minimum height of bookshelf needed to place all books
3 * @param books - Array of books where each book is [width, height]
4 * @param shelfWidth - Maximum width of each shelf
5 * @returns Minimum total height of the bookshelf
6 */
7function minHeightShelves(books: number[][], shelfWidth: number): number {
8 const bookCount: number = books.length;
9
10 // dp[i] represents minimum height needed to place first i books
11 const dp: number[] = new Array(bookCount + 1).fill(0);
12
13 // Process each book
14 for (let currentBookIndex = 1; currentBookIndex <= bookCount; currentBookIndex++) {
15 // Get current book dimensions
16 let [currentWidth, currentHeight] = books[currentBookIndex - 1];
17
18 // Initialize: place current book on a new shelf
19 dp[currentBookIndex] = dp[currentBookIndex - 1] + currentHeight;
20
21 // Try placing current book with previous books on the same shelf
22 for (let previousBookIndex = currentBookIndex - 1; previousBookIndex > 0; previousBookIndex--) {
23 // Add width of the previous book
24 currentWidth += books[previousBookIndex - 1][0];
25
26 // Check if total width exceeds shelf width
27 if (currentWidth > shelfWidth) {
28 break;
29 }
30
31 // Update maximum height for current shelf
32 currentHeight = Math.max(currentHeight, books[previousBookIndex - 1][1]);
33
34 // Update minimum height if placing books together is better
35 dp[currentBookIndex] = Math.min(dp[currentBookIndex], dp[previousBookIndex - 1] + currentHeight);
36 }
37 }
38
39 return dp[bookCount];
40}
41
Time and Space Complexity
The time complexity of this algorithm is O(n²)
, where n
is the length of the books
array. This is because:
- The outer loop iterates through all
n
books (from index 1 to n) - For each book at position
i
, the inner loop iterates backwards fromi-1
to 1 in the worst case - This creates a nested loop structure where for each of the
n
iterations of the outer loop, we potentially have up toi-1
iterations of the inner loop - The total number of operations is approximately
1 + 2 + 3 + ... + (n-1) = n(n-1)/2
, which simplifies toO(n²)
The space complexity is O(n)
. The algorithm uses:
- An array
f
of sizen+1
to store the dynamic programming states, which requiresO(n+1) = O(n)
space - A few additional variables (
i
,j
,w
,h
) that use constant spaceO(1)
- The overall space complexity is therefore
O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Off-by-One Errors with Book Indexing
The most common mistake occurs when mixing 0-based array indexing with 1-based DP array indexing. Since books
is 0-indexed but dp[i]
represents the first i
books (1-indexed), developers often incorrectly access book properties.
Incorrect approach:
for i in range(1, n + 1):
dp[i] = dp[i - 1] + books[i][1] # Wrong! books[i] is out of bounds when i = n
Correct approach:
for i in range(1, n + 1):
dp[i] = dp[i - 1] + books[i - 1][1] # Correct: books[i-1] for the i-th book
2. Incorrect Width Accumulation Order
When iterating backwards to try different shelf combinations, developers might add the current book's width multiple times or forget to include it initially.
Incorrect approach:
for i in range(1, n + 1):
dp[i] = dp[i - 1] + books[i - 1][1]
cumulative_width = 0 # Wrong! Missing current book's width
max_height = 0 # Wrong! Missing current book's height
for j in range(i - 1, 0, -1):
cumulative_width += books[j - 1][0]
# This never includes book i's width!
Correct approach:
for i in range(1, n + 1):
width, height = books[i - 1]
dp[i] = dp[i - 1] + height
cumulative_width = width # Start with current book
max_height = height # Start with current book's height
for j in range(i - 1, 0, -1):
cumulative_width += books[j - 1][0]
# Now correctly accumulates all books from j to i
3. Forgetting to Update Maximum Height
When grouping books on the same shelf, some implementations forget to continuously update the maximum height as they add more books to the shelf.
Incorrect approach:
for j in range(i - 1, 0, -1):
cumulative_width += books[j - 1][0]
if cumulative_width > shelfWidth:
break
# Forgot to update max_height!
dp[i] = min(dp[i], dp[j - 1] + height) # Uses original height only
Correct approach:
for j in range(i - 1, 0, -1):
cumulative_width += books[j - 1][0]
if cumulative_width > shelfWidth:
break
max_height = max(max_height, books[j - 1][1]) # Update shelf height
dp[i] = min(dp[i], dp[j - 1] + max_height) # Use updated max_height
4. Breaking Too Early in the Inner Loop
Some implementations might use continue
instead of break
when the shelf width is exceeded, leading to incorrect results by skipping only one iteration instead of stopping entirely.
Incorrect approach:
for j in range(i - 1, 0, -1):
cumulative_width += books[j - 1][0]
if cumulative_width > shelfWidth:
continue # Wrong! This skips current j but continues with j-1
max_height = max(max_height, books[j - 1][1])
dp[i] = min(dp[i], dp[j - 1] + max_height)
Correct approach:
for j in range(i - 1, 0, -1):
cumulative_width += books[j - 1][0]
if cumulative_width > shelfWidth:
break # Correct: Stop trying to add more books to this shelf
max_height = max(max_height, books[j - 1][1])
dp[i] = min(dp[i], dp[j - 1] + max_height)
Which of the following is a min heap?
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!