Maximum Number of Books You Can Take

Updated on 16 June, 2025
Maximum Number of Books You Can Take header image

Problem Statement

In this challenge, you are provided with an array books representing the number of books on each shelf of a bookshelf, indexed from 0 to n-1. The task is to select books from a contiguous subset of shelves, denoted by indices l to r, such that for each shelf i in the range l to r - 1, the number of books you take from shelf i must be strictly less than the number taken from shelf i + 1.

Your goal is to determine the maximum total number of books you can collect under these constraints.

Examples

Example 1

Input:

books = [8,5,2,7,9]

Output:

19

Explanation:

- Take 1 book from shelf 1.
- Take 2 books from shelf 2.
- Take 7 books from shelf 3.
- Take 9 books from shelf 4.
Total = 1 + 2 + 7 + 9 = 19.

Example 2

Input:

books = [7,0,3,4,5]

Output:

12

Explanation:

- Take 3 books from shelf 2.
- Take 4 books from shelf 3.
- Take 5 books from shelf 4.
Total = 3 + 4 + 5 = 12.

Example 3

Input:

books = [8,2,3,7,3,4,0,1,4,3]

Output:

13

Explanation:

- Take 1 book from shelf 0.
- Take 2 books from shelf 1.
- Take 3 books from shelf 2.
- Take 7 books from shelf 3.
Total = 1 + 2 + 3 + 7 = 13.

Constraints

  • 1 <= books.length <= 10⁵
  • 0 <= books[i] <= 10⁵

Approach and Intuition

The core challenge is to find a contiguous subarray in which we can select books in strictly increasing quantities, such that the selected quantity from each shelf does not exceed the shelf's available books.

To maximize the total number of books taken:

  1. Use a Stack or Monotonic Pattern:

    • Iterate backward through books, maintaining a stack to simulate increasing sequences from right to left.
    • Greedily attempt to take as many books as allowed while ensuring the strictly increasing constraint.
  2. Greedy Backward Sweep:

    • Let books[i] be the current shelf.
    • Keep track of the maximum number of books you are allowed to take at this position (limit). Start with infinity.
    • At each step, set take = min(books[i], limit).
    • Accumulate the total and update limit = take - 1.
    • Stop when limit == 0.
  3. Slide Over All Endpoints:

    • Try each possible endpoint r from right to left and simulate collecting books backward to some l, computing the total for that range.
    • Keep track of the maximum total found over all iterations.

This strategy ensures we explore all valid ranges efficiently and find the one that yields the highest number of books under the increasing constraint.

Time Complexity

  • The algorithm runs in O(n) due to linear traversal and greedy bookkeeping, suitable for the constraint n <= 10⁵.

Solutions

  • C++
  • Java
  • Python
cpp
class Solution {
public:
    long long getMaxBooks(vector<int>& shelf) {
        int len = shelf.size();
    
        // Lambda to compute sum of books between two indices inclusive
        auto calcSum = [&](int start, int end) {
            long long maxCnt = min(shelf[end], end - start + 1);
            return (2 * shelf[end] - (maxCnt - 1)) * maxCnt / 2;
        };
    
        stack<int> indices;
        vector<long long> dp(len);
    
        for (int i = 0; i < len; i++) {
            // Maintain the conditions for the stack
            while (!indices.empty() && shelf[indices.top()] - indices.top() >= shelf[i] - i) {
                indices.pop();
            }
    
            // Calculate dp[i] based on previous values
            if (indices.empty()) {
                dp[i] = calcSum(0, i);
            } else {
                int prev = indices.top();
                dp[i] = dp[prev] + calcSum(prev + 1, i);
            }
    
            // Add current index to stack
            indices.push(i);
        }
    
        // Find the maximum value in dp
        return *max_element(dp.begin(), dp.end());
    }
};

This solution, implemented in C++, is designed to find the maximum number of books one can take from a series of bookshelves represented by a vector of integers, where each integer specifies the number of books that can be taken from that particular bookshelf. The approach uses dynamic programming to optimize the process, complemented by a stack to help maintain needed indices for state transitions.

  • Start by initializing dp, a dynamic programming vector to store maximum books that can be acquired up to each index.
  • Utilize a stack to help keep track of indices that are potentially beneficial for future states. The stack ensures that the properties of the solution are consistent, especially when calculating sums over certain ranges efficiently.
  • Use a lambda function calcSum to efficiently calculate the maximum books from a specific range of bookshelves. It computes the possible number of books considering the bounds set by the last shelf in the current segment.
  • As the solution iterates over each bookshelf, it determines the value of dp for each location either by calculating a sum from the beginning using calcSum when the stack is empty, or based on a relative calculation derived from the last relevant position stored in the stack. After computing the value for dp[i], the current index i is pushed onto the stack.
  • Once the iteration completes, the maximum value in the dp vector is found and returned. This value represents the maximum number of books that can be taken.

Overall, this efficient approach combines dynamic programming with clever usage of a stack and lambda expressions to dynamically and optimally solve the problem of selecting the maximum number of books across a series of bookshelves. The algorithm judiciously balances between recalculating sums and using pre-calculated values to ensure optimal performance.

java
class Solution {
    public long maxBooksStack(int[] booksArray) {
        int length = booksArray.length;
    
        Stack<Integer> indexStack = new Stack<>();
        long[] dpResults = new long[length];
    
        for (int index = 0; index < length; index++) {
            while (!indexStack.isEmpty() && booksArray[indexStack.peek()] - indexStack.peek() >= booksArray[index] - index) {
                indexStack.pop();
            }
    
            if (indexStack.isEmpty()) {
                dpResults[index] = sumOfBooks(booksArray, 0, index);
            } else {
                int previousIndex = indexStack.peek();
                dpResults[index] = dpResults[previousIndex] + sumOfBooks(booksArray, previousIndex + 1, index);
            }
    
            indexStack.push(index);
        }
    
        return Arrays.stream(dpResults).max().getAsLong();
    }
    
    private long sumOfBooks(int[] booksArray, int start, int end) {
        long count = Math.min(booksArray[end], end - start + 1);
        return (2 * booksArray[end] - (count - 1)) * count / 2;
    }
}

The solution provided outlines an efficient method to find the maximum number of books one can take under certain conditions, implemented in Java. The problem uses a dynamic programming approach with a stack and the following steps provide an overview of the solution:

  1. Initialize an array dpResults to store the results of dynamic programming and a Stack<Integer> named indexStack to help determine range boundaries for subproblems efficiently.

  2. Using a for loop, iterate through each book count in the array booksArray. During each iteration:

    • While indexStack is not empty and the current book's adjusted position is less or equal to the adjusted position at the peek of indexStack, pop the stack.
    • If indexStack is empty after the pop operations, compute the sum of books from the start to the current index using a helper method sumOfBooks. Otherwise, use the last index from indexStack to compute and add the sum of books between this index and current.
    • Push the current index onto indexStack.
  3. After filling up the dpResults, use Java's Arrays.stream to find and return the maximum value.

The helper method sumOfBooks, calculates the sum based on a mathematical formula considering the position and number of books from a specified range inducing optimization in the repetitive addition tasks by leveraging arithmetic progression principles.

This systematic approach takes into account the peculiarities of each book's position related to its count while ensuring efficiency using dynamic programming and a stack-based mechanism. Remember, the implementation correctly calculates the maximum sum by adjusting the array-based computations and using a stack to maintain relevant indices for subproblems. This results in optimal time complexity suitable for the problem's requirements.

python
class Solution:
    def maxBooksCollected(self, shelves: List[int]) -> int:
        shelfCount = len(shelves)
    
        # Customize function to compute sum of books from index left to right
        def getSumBooks(left, right):
            books = min(shelves[right], right - left + 1)
            return (2 * shelves[right] - (books - 1)) * books // 2
    
        indexStack = []
        bookDp = [0] * shelfCount
    
        for index in range(shelfCount):
            while indexStack and shelves[indexStack[-1]] - indexStack[-1] >= shelves[index] - index:
                indexStack.pop()
    
            if not indexStack:
                bookDp[index] = getSumBooks(0, index)
            else:
                lastValid = indexStack[-1]
                bookDp[index] = bookDp[lastValid] + getSumBooks(lastValid + 1, index)
    
            indexStack.append(index)
    
        return max(bookDp)

This Python solution focuses on solving the problem of finding the maximum number of books one can take from a series of shelves represented by an array, where each element denotes the maximum height of books that can be taken from that particular shelf.

The function maxBooksCollected computes the result using dynamic programming and a stack data structure to optimize the process. Here’s a brief overview of the solution implementation:

  • Dynamic Programming Table Initialization: A list bookDp is initialized with zeros to store the maximum number of books that can be taken up to each shelf.

  • Stack for Index Tracking: An empty list indexStack is used to keep track of indices for shelves, helping in efficient computation of book numbers using previous result values.

  • Helper Function - getSumBooks: This function calculates the maximum books that can be taken between two specified indices. It utilizes the mathematical formula involving the minimum value constraint on the shelves and computes a triangular number to sum up the books.

  • Logic within the Loop:

    1. The loop iterates through each shelf index.
    2. It adjusts the indexStack by popping indices to maintain correct bounds for the maximum books calculation from the current index.
    3. Depending on whether the stack is empty or not, it calculates the sum of books up to the current shelf using the getSumBooks function directly or adds it to the sum from a previously valid index stored in bookDp.
    4. Current index is then added to the stack for future reference.
  • Final Result: After processing all shelves, the function returns the maximum value from bookDp, which represents the maximum number of books that can be taken from the shelves.

This solution efficiently utilizes dynamic programming alongside a stack to manage and compute the required values dynamically, ensuring optimal performance even with larger inputs. The use of auxiliary space for DP storage and index management helps in maintaining an efficient and comprehensible algorithm flow.

Comments

No comments yet.