Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Each painter paints a contiguous block of boards, so the finishing time is the largest block-length sum across the k painters. Binary-search that answer: for a candidate time limit x, greedily assign boards left to right and count how many painters x forces. 'Can k painters finish if nobody works more than x?' is monotonic in x, so binary search finds the smallest feasible x in O(n log(sum)). Bounds are [max(board), sum(boards)].
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you recognize this as the same min-max-over-contiguous-groups problem as Allocate Pages, pin the answer bounds at [max(board), sum(boards)], and justify the monotone feasibility predicate before coding.
Where candidates slip
Sorting the boards (which destroys contiguity), using lo = 0, or moving the search the wrong way on a feasible mid because the minimize/maximize direction got flipped.
How to stand out
Name it explicitly: 'this is Allocate Pages / Split Array Largest Sum'. State that feasible(time) is monotone increasing, justify lo = max(board), and fold any per-unit time factor in at the very end.
Choose
Compare the available approaches
Use constraints and trade-offs to select the route you can defend in an interview.
Interactive decision treeInteractiveChoose the right approachFirst decision previewMinimize the time of the SLOWEST painterpainters needed with cap x ≤ k ?FeasibleInfeasibleExplore the full treeCollapse the tree
Learn & trace
Build each solution step by step
Move from the core idea to a visual trace, implementation, proof, and edge cases.
Approach 1 of 2
Linear Scan Over the Answer — Baseline
1Hint 1Explore hintHide hint
The answer is some finishing time between max(boards) and sum(boards). What if you tried every time cap upward and returned the first that works?
2Hint 2Explore hintHide hint
For a fixed cap x, count painters: walk the boards, keep a running block sum, and start a new painter whenever the next board would push the block past x.
3Hint 3Explore hintHide hint
The first cap whose painter-count is ≤ k is the answer — and once a cap works, every larger cap works. But sum(boards) can be ~10⁹, so the scan dominates.
A · Core idea
Start with the reframing: you're choosing a number — the time the slowest painter is allowed to take — and that number lives in [max(boards), sum(boards)]. The lower end is max(boards) because whoever paints the longest board needs at least that long; the upper end is sum(boards) because a single painter could do every board.
The feasibility check counts painters. Fix a time cap x. Sweep the boards left to right with a running block sum. Add the current board; if that would exceed x, close the current painter's block and hand this board to a new painter. The painters opened is exactly the minimum needed under cap x, because the assignment is contiguous and lengths are non-negative — packing each painter as full as the cap allows minimizes how many painters you use.
Why greedy-fill is exact. Within a contiguous block the running sum only grows, and pushing a painter to work right up to the cap never forces more painters downstream — it can only leave less for later, which helps. So 'fill to the cap, then cut' yields the fewest painters for that cap — a precise yes/no feasibility answer, not an estimate.
Why scanning upward is valid. The first cap whose painter count is ≤ k is the minimum feasible time. Raising the cap only lets each painter absorb more boards, so it never increases the painter count — meaning once a cap works, all larger caps work. That 'larger cap ⇒ never worse' fact is the monotonicity we'll exploit next.
Why we don't ship this. sum(boards) can reach 10⁵ · 10⁴ = 10⁹. An O(n) check run a billion times is hopeless. Because feasibility is monotone in the cap, binary-search the boundary instead of walking to it.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
boards = [10, 20, 30, 40], k = 2Array state
painter 1
10 → 30 → 60 (all ≤ 60)
60C · Code with teaching comments
Reference implementation previewpublic int paintersPartition(int[] boards, int k) { int lo = 0, hi = 0; for (int b : boards) { lo = Math.max(lo, b); hi += b; } for (int x = lo; x <= hi; x++) { if (paintersNeeded(boards, x) <= k) {Explore the full codeHide the full code
D · Why it works and complexityO(n · sum) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
The fact that 'a larger time cap never needs more painters' is precisely the proof that feasible(x) is monotone increasing — the green light to replace the linear scan with a binary search over the same candidate caps.
ComplexityO(n · sum) time, O(1) space — Each candidate time (up to sum(boards)) runs an O(n) greedy painter count → O(n · sum). Fine for tiny totals, far too slow under the real constraints.
E · Edge cases and pitfalls3Starting the scan at 0 instead of max(boards) — wastes time on impossible caps.Explore edge casesHide edge cases
Pitfalls
- Starting the scan at 0 instead of max(boards) — wastes time on impossible caps.
- Letting x climb to sum(boards) ≈ 10⁹ — the scan times out.
- Sorting boards 'to balance' — destroys contiguity and changes the problem.
Approach 2 of 2 · Optimal
Binary Search on the Finishing Time (Optimal)
1Hint 1Explore hintHide hint
You showed feasible(time) is false for small caps and true for large caps, flipping exactly once. What finds that flip in logarithmic time?
2Hint 2Explore hintHide hint
Binary-search the time cap in [max(boards), sum(boards)]. At each mid, run the O(n) greedy painter count. ≤ k painters means mid is feasible — record it and try smaller; otherwise try larger.
3Hint 3Explore hintHide hint
Because you MINIMIZE, on a feasible mid record ans = mid and move hi = mid - 1; on infeasible move lo = mid + 1. The last feasible mid is the answer.
A · Core idea
This is binary search on the answer, identical in structure to Allocate Pages — only the words 'students/pages' become 'painters/time'.
The monotonic predicate, precisely. Define feasible(x) = true if the boards split into at most k contiguous blocks each summing to ≤ x. Raising x never increases the painters forced (a roomier cap only lets each painter take more boards). So across x from max(boards) to sum(boards), feasible(x) reads [F, F, …, F, T, T, …, T] — false while too tight, true once loose enough, flipping exactly once. The answer is the smallest `x` that is true.
The feasibility check is the same painter count. paintersNeeded(x) sweeps once, accumulating a block load and opening a new painter whenever the next board would push the load past x. Contiguity plus non-negative lengths make filling-to-the-cap the painter-minimizing strategy, so the count is exact — the hard requirement for binary-search-on-the-answer.
Driving toward the smallest true. Set lo = max(boards), hi = sum(boards). Compute mid = lo + (hi − lo) / 2. If paintersNeeded(mid) ≤ k, mid works but a tighter cap might too — record ans = mid and search the lower half with hi = mid − 1. If mid needs more than k painters, it's too tight — search upward with lo = mid + 1. When lo > hi, ans holds the smallest feasible time.
The off-by-one traps, named. (1) lo = 0 instead of max(boards) wastes iterations on caps that can't even hold the longest board. (2) Recording ans on the infeasible branch returns a time that doesn't actually fit k painters. (3) Flipping the direction — this is minimize the maximum, so a feasible mid means 'try smaller', i.e. move hi left. Going right returns a larger-than-necessary time. State the invariant 'ans is always a time that fits within k painters' and the moves follow.
Folding in per-unit time. If each unit length takes t time, the finishing time is t × (min-max block sum). Solve the integer problem above, then multiply the result by t once at the end — the search itself is unchanged.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
boards = [10, 20, 30, 40], k = 2Array state
lo=40, hi=100
mid=70 feasible(T) → ans=70, hi=69
60C · Code with teaching comments
D · Why it works and complexityO(n log(sum)) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
Painter's Partition, Allocate Pages, and Split Array Largest Sum are the same algorithm; only the nouns differ. Once you can say 'minimize the maximum over a contiguous k-way split', you should write the [max, sum] binary search from muscle memory. The transferable skill is recognising that disguise, not re-deriving the search each time.
ComplexityO(n log(sum)) time, O(1) space — The time range spans sum(boards), so binary search runs O(log(sum)) iterations, each doing the O(n) greedy count → O(n log(sum)). Computing bounds is O(n); space is O(1).
E · Edge cases and pitfalls7boards = [100], k = 1 — lo = hi = 100; the single painter takes 100 → answer 100.Explore edge casesHide edge cases
Edge cases handled
boards = [100], k = 1lo = hi = 100; the single painter takes 100 → answer 100.boards = [1, 1, 1, 1, 1], k = 5One board each → slowest painter takes 1. lo = 1 is already feasible → answer 1.boards = [3, 1, 4, 1, 5], k = 1One painter does everything → answer sum = 14, the upper bound.
Pitfalls
- Setting lo = 0 instead of max(boards) — searches times that can't paint the longest board.
- Recording ans on the infeasible branch — returns a time that needs more than k painters.
- Going right on a feasible mid — that maximizes instead of minimizing.
- Using (lo + hi) / 2 — overflows when sum approaches the int limit.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic int paintersPartition(int[] boards, int k) { int lo = 0, hi = 0; for (int b : boards) { lo = Math.max(lo, b); hi += b; } int ans = hi; while (lo <= hi) {Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Sorting the boards
Painters take contiguous boards in the given order. Sorting reorders the line, so the blocks you count no longer correspond to valid assignments.
Explore the code comparisonHide the code comparison
Sorting the boards
Painters take contiguous boards in the given order. Sorting reorders the line, so the blocks you count no longer correspond to valid assignments.
Don't write this
Arrays.sort(boards); // WRONG: contiguity is defined by original order
int lo = boards[boards.length - 1], hi = ...;Write this instead
int lo = 0, hi = 0;
for (int b : boards) { lo = Math.max(lo, b); hi += b; } // no sort2Wrong lower bound
The painter assigned the longest board can't finish before that board is done. Starting lo at 0 explores times that can never be feasible.
Explore the code comparisonHide the code comparison
Wrong lower bound
The painter assigned the longest board can't finish before that board is done. Starting lo at 0 explores times that can never be feasible.
Don't write this
lo, hi = 0, sum(boards) # lo too lowWrite this instead
lo, hi = max(boards), sum(boards)3Wrong direction on a feasible mid
This minimizes the maximum, so a feasible time means 'a smaller time might also work'. Moving lo right returns a finishing time larger than the true minimum.
Explore the code comparisonHide the code comparison
Wrong direction on a feasible mid
This minimizes the maximum, so a feasible time means 'a smaller time might also work'. Moving lo right returns a finishing time larger than the true minimum.
Don't write this
if (paintersNeeded(boards, mid) <= k) {
lo = mid + 1; // WRONG: walks away from smaller feasible times
}Write this instead
if (paintersNeeded(boards, mid) <= k) {
ans = mid;
hi = mid - 1; // minimize → search for an even smaller time
}Revise
Carry the pattern into the interview
Pattern to remember
Binary search on the answer + greedy painter-count feasibility
Code pattern to memorize
lo, hi = max(boards), sum(boards)
while lo <= hi:
mid = lo + (hi - lo) // 2
if paintersNeeded(mid) <= k: # painters required with time cap mid
ans = mid; hi = mid - 1 # feasible → try a SMALLER time
else:
lo = mid + 1 # infeasible → allow MORE timeRules to remember
- 1A painter paints a contiguous run of boards — that contiguity is what lets a single left-to-right sweep count the painters a time cap forces.
- 2Lower bound is
max(board): whoever paints the longest single board needs at least that much time. Upper bound issum(boards): one painter does everything. - 3The predicate is monotone increasing: if a time cap
xis enough, every cap> xis enough too. We MINIMIZE, so on a feasible mid go left (hi = mid − 1) and record it. - 4If boards have a per-unit time
t, the answer is justt ×(min-max block sum) — foldtin at the end; it doesn't change the search. - 5Feasibility counts painters forced by the cap: open a new painter whenever the next board would push the running block past the cap. Bail early once the count exceeds k.
Use this pattern when
- Workers/painters/machines each take a CONTIGUOUS slice of a sequence
- You minimize the slowest worker's load — minimize the maximum block sum
- A fast O(n) check answers 'how many workers does this load cap force?'
- The answer is a number in [max(element), sum(elements)]
Choose another pattern when
- Painters can take non-contiguous boards → it's a scheduling/partition problem, not binary search on the answer
- You must output the assignment, not just the finishing time → reconstruct after the search
- Board lengths can be negative → running sums aren't monotone and the greedy count breaks
- k is effectively unbounded → the answer is just max(board)
Interview takeaway
Painter's Partition is Allocate Pages wearing a different hat: minimize the largest contiguous block sum across k groups. Recognise the contiguous-split + min-max signature, bound the answer by [max, sum], binary-search it, and check feasibility with a one-pass greedy worker count. Same algorithm as Split Array Largest Sum.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternCanonical Binary Search on the Answer (minimize the maximum). Trigger on 'k workers each take a contiguous slice, minimize the slowest worker's load'. Bounds [max(element), sum(elements)]; feasibility is an O(n) greedy worker count. Identical to Allocate Pages and Split Array Largest Sum.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Allocate Minimum PagesSame algorithm — k students, book pages, minimize the maximum pages per student.
- Split Array Largest SumComing soonLeetCode's version: split into k subarrays minimizing the largest subarray sum.
- Aggressive CowsThe mirror image — maximize the minimum gap; feasible monotone the other way.
- Capacity to Ship Packages Within D DaysComing soonBinary-search the ship capacity; feasible(cap) = all packages ship within D days.