Topics › Binary Search › BS on Answers
Book Allocation Problem
Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given n books in a row, where book i has pages[i] pages, and m students. Allocate the books so that:
- every book goes to exactly one student,
- every student receives at least one book,
- each student receives a contiguous block of books.
The load of a student is the total number of pages they receive. Minimise the maximum load over all students and return that value. If an allocation is impossible (more students than books), return -1.
Constraints: 1 ≤ n ≤ 10^5, 1 ≤ m ≤ 10^5, 1 ≤ pages[i] ≤ 10^6.
Example 1
pages = [12, 34, 67, 90], m = 2
answer = 113 // [12, 34, 67] and [90]: loads 113 and 90; the alternative [12, 34] and [67, 90] gives 157
Example 2
pages = [25, 46, 28, 49, 24], m = 4
answer = 71 // [25, 46] [28] [49] [24]: the heaviest load is 71
Example 3
pages = [1, 2, 3], m = 4
answer = -1 // four students, three books: someone gets nothing
The feasibility check
Fix a candidate limit maxPages. What is the fewest students needed so that no student exceeds the limit? Walk the books from left to right, giving each book to the current student while the running total stays within the limit; when the next book would push the total over, start a new student with that book. This greedy count is minimal because the blocks are contiguous and we only decide where to cut; postponing each cut as long as possible can never increase the number of blocks.
static int studentsNeeded(int[] pages, int maxPages) {
int students = 1;
long load = 0;
for (int p : pages) {
if (load + p > maxPages) { students++; load = 0; }
load += p;
}
return students;
}
The function assumes maxPages ≥ max(pages); otherwise a single book could never be placed. Both approaches start the search at max to guarantee that.
Impossibility
If m > n, some student would get no book, so return -1 immediately. Conversely, when m ≤ n, an allocation always exists, and any limit at which the greedy scan needs at most m students is achievable with exactly m students: a block that holds two or more books can be split into two blocks without raising any load, and since n ≥ m there are always enough books to keep splitting until every student has one. This is why the check below is studentsNeeded ≤ m rather than == m.
Approach 1: Brute force, try every limit
Intuition. The maximum load cannot be smaller than the thickest book (max), because that book must land on someone. It never needs to exceed the total number of pages (sum), the load when one student takes everything. A larger limit never needs more students, so scanning limits from max upwards and stopping at the first one that needs at most m students yields the minimum.
Algorithm.
- If
m > n, return-1. - Compute
maxandsumofpages. - For
limit = max, max + 1, ..., sum: ifstudentsNeeded(pages, limit) ≤ m, returnlimit. - Return
sum(never reached:limit = sumalways needs one student).
static int bruteForce(int[] pages, int m) {
int n = pages.length;
if (m > n) return -1;
int max = 0, sum = 0;
for (int p : pages) { max = Math.max(max, p); sum += p; }
for (int limit = max; limit <= sum; limit++) {
if (studentsNeeded(pages, limit) <= m) return limit;
}
return sum;
}
Complexity. Time O(n * (sum - max)): the range of limits can reach 10^11 with the stated constraints, each checked in O(n). Space O(1). Correct but unusable at scale.
Approach 2: Optimal, binary search on the limit
Intuition. Let ok(limit) be “the greedy scan needs at most m students”. If ok(limit) holds, then ok(limit + 1) holds too: the same blocks still respect the bigger limit. So over limit = max, ..., sum the predicate reads F F ... F T T ... T, and the answer is the first T. Binary search: when mid is feasible, record it and look for a smaller limit; otherwise look larger.
Algorithm.
- If
m > n, return-1. - Compute
maxandsum; setlo = max,hi = sum,ans = sum. - While
lo ≤ hi:mid = lo + (hi - lo) / 2.- If
studentsNeeded(pages, mid) ≤ m:ans = mid,hi = mid - 1. - Else:
lo = mid + 1.
- Return
ans.
static int optimal(int[] pages, int m) {
int n = pages.length;
if (m > n) return -1;
int max = 0, sum = 0;
for (int p : pages) { max = Math.max(max, p); sum += p; }
int lo = max, hi = sum, ans = sum;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
if (studentsNeeded(pages, mid) <= m) { ans = mid; hi = mid - 1; }
else lo = mid + 1;
}
return ans;
}
Dry run on pages = [12, 34, 67, 90], m = 2 (max = 90, sum = 203):
| lo | hi | mid | greedy blocks | students | ≤ 2? | ans | next |
|---|---|---|---|---|---|---|---|
| 90 | 203 | 146 | [12,34,67] [90] | 2 | yes | 146 | [90, 145] |
| 90 | 145 | 117 | [12,34,67] [90] | 2 | yes | 117 | [90, 116] |
| 90 | 116 | 103 | [12,34] [67] [90] | 3 | no | 117 | [104, 116] |
| 104 | 116 | 110 | [12,34] [67] [90] | 3 | no | 117 | [111, 116] |
| 111 | 116 | 113 | [12,34,67] [90] | 2 | yes | 113 | [111, 112] |
| 111 | 112 | 111 | [12,34] [67] [90] | 3 | no | 113 | [112, 112] |
| 112 | 112 | 112 | [12,34] [67] [90] | 3 | no | 113 | [113, 112] stop |
Result 113.
Complexity. Time O(n log(sum)): sum ≤ 10^11 means about 37 iterations of an O(n) scan. Space O(1).
A note on sum and long
With n = 10^5 books of 10^6 pages, sum is 10^11, which does not fit in an int. The code above keeps sum, lo, hi and mid as int, which is correct for the typical judge limits (sum ≤ 10^9) and for every test in the Java file. If your constraints allow sum beyond 2^31 - 1, change those four variables and the return type to long; the logic does not change. The running load inside studentsNeeded is already a long.
Edge cases and pitfalls
- More students than books. Return
-1before computing anything. The Java tests include[1, 2, 3]withm = 4. - One student. The answer is
sum. The search recordssumas the initialansand never finds a feasible smaller limit. - One book per student (
m == n). The answer ismax. Everymid < maxis excluded by the search range, andmid = maxneeds exactlynstudents. - Using
== min the predicate. A limit that needs fewer thanmstudents is still achievable withmstudents by splitting blocks, as argued above. Requiring equality makes the predicate non-monotonic and the search can miss the answer. - Starting
loat1or0. Belowmaxthe greedy scan happily puts an oversized book on a fresh student and reports a count, hiding the fact that the limit is violated. Always start atmax. - Wrong partition.
[10, 20, 30, 40]withm = 2is60([10, 20, 30] [40]), not70([10, 20] [30, 40]). The greedy counter finds the right cut because it postpones the split until the limit would be exceeded. - Contiguity. Books cannot be reordered. This is a partition problem, not a load-balancing (bin packing) problem.
Related problems
- Capacity to Ship Packages Within D Days is the identical problem with packages and days; the Painter’s Partition problem is another reskin of it.
- Aggressive Cows is the maximisation mirror image, useful for seeing how the search direction flips.
- Koko eating bananas is the gentlest introduction to the same “minimum feasible value” search.
- Minimum days to make M bouquets also returns
-1when the task is impossible, mirroring them > ncheck here.