Understand
30-second explanation
Build the interview-ready mental model before comparing implementations.
Each container can hold all toys whose weights lie in a window `[w, w + 4]` for some chosen base weight `w`. To minimise the number of containers, sort the weights ascending and greedily open a new container at the smallest uncovered weight; it then covers everything up to that weight + 4. Skip every toy within the window and repeat at the next uncovered toy. O(n log n) from the sort.
Interview contextWhat is testedWhere candidates slipHow to stand outExplore interview contextHide interview context
What's being tested
Whether you reframe 'fewest containers' as 'fewest fixed-width intervals covering all points', sort, and anchor greedily — plus getting the inclusive boundary (w + 4) right.
Where candidates slip
Centering the window on a weight instead of anchoring at the smallest uncovered one, or using `>=` so a toy exactly at the boundary spawns an unnecessary container.
How to stand out
Give the exchange argument: anchoring the window at the leftmost uncovered toy dominates any later anchor, since moving the anchor left never loses coverage and may gain it.
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 previewWeightsAny toy > w + 4?YesNoExplore 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
Brute Force — Try Anchoring Windows at Every Subset
1Hint 1Explore hintHide hint
A solution chooses a set of window anchors so that every toy lies in some [anchor, anchor + 4]. Without insight, you might search over which toys start windows.
2Hint 2Explore hintHide hint
Try every subset of toys as potential window starts, check that the chosen windows cover all toys, and keep the smallest valid subset.
3Hint 3Explore hintHide hint
There are 2ⁿ subsets — only feasible for tiny n. It exists purely to motivate sorting and the leftmost-anchor greedy.
A · Core idea
Imagine deciding, for every toy, whether it 'starts' a container window. Any chosen set of starts defines windows [w, w + 4]; the set is valid if every toy lies inside at least one window. Search all 2ⁿ subsets and keep the smallest valid one.
Why it's correct. Every possible placement of windows is considered (a window only ever needs to start at some toy's weight — starting between toys wastes reach), so the minimum valid subset is the true answer.
Why it's hopeless. 2ⁿ subsets is exponential; even n = 30 is a billion. The constraint allows n = 100000, so this never runs.
The structure it reveals. Notice a window never benefits from starting to the left of the smallest toy it must cover — that just wastes reach. So the optimal anchors are always toys themselves, processed left to right, each new window opened exactly at the next uncovered toy. That observation removes the search entirely.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
w = [1, 2, 3, 4]anchors {1}
window [1,5] covers 1,2,3,4 → valid, size 1
best = 11C · Code with teaching comments
Reference implementation previewpublic int toysBrute(int[] w) { Arrays.sort(w); int n = w.length, best = n; for (int mask = 1; mask < (1 << n); mask++) { boolean[] covered = new boolean[n];Explore the full codeHide the full code
D · Why it works and complexityO(2ⁿ · n) timeO(n) spaceOpen the invariant and proofExplore the proofHide the proof
A window should always be anchored at an actual uncovered toy (anchoring earlier wastes its 4-unit reach). That single observation turns a 2ⁿ subset search into a left-to-right sweep.
ComplexityO(2ⁿ · n) time, O(n) space — 2ⁿ candidate anchor subsets, each checked for full coverage in O(n). Exponential and unusable beyond small n.
E · Edge cases and pitfalls32ⁿ explosion — only for n ≤ ~20.Explore edge casesHide edge cases
Pitfalls
- 2ⁿ explosion — only for n ≤ ~20.
- Anchoring windows between toys wastes reach and never helps.
- Forgetting the boundary is inclusive (w + 4 is covered).
Approach 2 of 2 · Optimal
Greedy — Sort and Sweep Fixed-Width Windows (Optimal)
1Hint 1Explore hintHide hint
Sort the weights ascending. The smallest toy must be in some container — anchor that container's window at it so it reaches as far right as possible.
2Hint 2Explore hintHide hint
That window covers everything up to (smallest weight + 4). Skip all those toys; they cost nothing more.
3Hint 3Explore hintHide hint
The next uncovered toy forces a new container. Repeat. Count how many containers you open.
A · Core idea
Sort ascending. Sweep left to right. Keep the upper bound last + 4 of the current container's window (start with last = −∞). When a toy's weight exceeds last + 4, it can't fit the current window, so open a new container anchored at this toy and set last to its weight. Count the opens.
Why anchor at the smallest uncovered toy — the exchange argument. The leftmost uncovered toy w must go into some container, whose window is [base, base + 4] with base ≤ w. Slide that window right until base = w. Since every other uncovered toy is ≥ w, raising base to w never drops a toy that was already inside (those toys were ≥ base and now still ≥ w only if... ) — more precisely, the window [w, w + 4] covers at least as many of the remaining (uncovered, hence ≥ w) toys as any window with base < w, because the only toys a lower base could additionally cover are < w, and there are none left. So anchoring exactly at the smallest uncovered toy is never worse; it covers the longest possible prefix of the remaining toys. Each greedy container therefore covers a maximal run, and any optimal solution can be transformed into this one without increasing the count.
Why the strict `>` boundary. A toy at exactly last + 4 differs from the anchor by 4, which is allowed, so it stays in the current window. Using >= would wrongly open a new container for it.
Why sorting is required. The argument 'all remaining toys are ≥ the anchor' only holds if we process in non-decreasing order. Without sorting, a later small toy could appear and break the prefix-covering logic.
B · Visual dry run
Interactive dry run
Select a frame to connect the decision with its visual state.
w = [1, 2, 3, 21, 7, 12, 14, 21]Array state
open #1 at 1
covers [1,5] → 1,2,3
4C · Code with teaching comments
D · Why it works and complexityO(n log n) timeO(1) spaceOpen the invariant and proofExplore the proofHide the proof
Minimum cover by fixed-width intervals = sort + leftmost-anchor sweep. The reusable kernel: when intervals can slide freely, always anchor the next one at the leftmost uncovered point — it dominates every other placement on the remaining points.
ComplexityO(n log n) time, O(1) space — Sorting is O(n log n) and dominates; the single sweep is O(n) with only two scalar variables → O(1) extra space.
E · Edge cases and pitfalls6single toy — One window opens at it → 1 container.Explore edge casesHide edge cases
Edge cases handled
single toyOne window opens at it → 1 container.all equal weightsFirst toy opens one window covering the rest → 1 container.weights 4 apart, e.g. [0,4,8]0 covers ≤4 (includes 4); 8 > 4 opens a second → result 2, confirming inclusive boundary.
Pitfalls
- Using `>=` instead of `>` — splits a toy that is exactly 4 away into its own container.
- Forgetting to sort — the leftmost-anchor argument fails on unsorted input.
- Re-anchoring at every toy instead of only uncovered ones — overcounts containers.
Practice
Try the solution yourself
Edit the optimal implementation, test another input, and observe the result.
Editable code previewpublic int toys(int[] w) { Arrays.sort(w); int units = 0; long last = Long.MIN_VALUE; for (int weight : w) {Explore the playgroundClose the playground
Avoid mistakes
Recognize the interview traps
Understand why common attempts fail before reviewing the corrected implementation.
1Wrong boundary comparison
A toy at exactly last + 4 has a weight difference of 4, which is allowed. Using >= opens an unnecessary container.
Explore the code comparisonHide the code comparison
Wrong boundary comparison
A toy at exactly last + 4 has a weight difference of 4, which is allowed. Using >= opens an unnecessary container.
Don't write this
if weight >= last + 4:
units += 1
last = weightWrite this instead
if weight > last + 4:
units += 1
last = weight2Forgetting to sort
The greedy relies on always seeing the smallest uncovered toy first. Unsorted input breaks the prefix-covering guarantee.
Explore the code comparisonHide the code comparison
Forgetting to sort
The greedy relies on always seeing the smallest uncovered toy first. Unsorted input breaks the prefix-covering guarantee.
Don't write this
int units = 0; long last = Long.MIN_VALUE;
for (int weight : w) { if (weight > last + 4) { units++; last = weight; } }Write this instead
Arrays.sort(w);
int units = 0; long last = Long.MIN_VALUE;
for (int weight : w) { if (weight > last + 4) { units++; last = weight; } }3Anchoring on a fixed window grid
Snapping windows to multiples of 5 (e.g. [0,4],[5,9],…) can split a tight cluster across two containers, overcounting.
Explore the code comparisonHide the code comparison
Anchoring on a fixed window grid
Snapping windows to multiples of 5 (e.g. [0,4],[5,9],…) can split a tight cluster across two containers, overcounting.
Don't write this
units = len({weight // 5 for weight in w}) # fixed grid bucketsWrite this instead
w = sorted(w); units = 0; last = float('-inf')
for weight in w:
if weight > last + 4:
units += 1; last = weightRevise
Carry the pattern into the interview
Pattern to remember
Greedy interval covering: anchor each window at the smallest uncovered point
Code pattern to memorize
sort weights ascending
units = 0
last = -infinity
for w in weights:
if w > last + 4: # not covered by the current window
units += 1
last = w # open a new window [w, w + 4]
return unitsRules to remember
- 1A single container covers a contiguous weight window of size 4: any toys with weight in
[w, w + 4]for the chosen basew. - 2Sort ascending so you always meet the smallest uncovered toy first.
- 3Anchor each new window at that smallest uncovered toy
w; it greedily covers up tow + 4. - 4Use
w > last + 4(strictly greater) — a toy exactly atlast + 4is still inside the window. - 5Each container is unlimited in count and weight; the only limit is the 4-unit spread of weights it holds.
Use this pattern when
- Cover all points on a line with the fewest fixed-width intervals
- Each interval can be positioned freely but has a fixed reach
- Sorting reveals that anchoring at the leftmost uncovered point is optimal
- Minimising the count of groups, each spanning a bounded range
Choose another pattern when
- Intervals have fixed positions you cannot move → interval scheduling, not covering
- Capacity is by count or total weight, not by spread → bin packing / knapsack
- The window width varies per container → different DP
- You must maximise coverage under a fixed number of intervals → a different objective
Interview takeaway
Minimum fixed-width interval cover of points on a line: sort, then greedily anchor each interval at the leftmost uncovered point and jump past its reach. Anchoring at the smallest uncovered point is provably optimal — it covers the most while wasting none of the window to the left.
Continue
Transfer the pattern to another problem
Use a direct variation or pattern sibling to prove the idea is reusable.
Transferable patternMinimum fixed-width interval cover of points on a line: sort ascending, anchor each interval at the leftmost uncovered point, jump past its reach. The exchange argument shows the leftmost anchor dominates, making the sweep optimal.
Practice queueDirect variations and pattern siblings
Direct follow-ups
- Greedy FloristSort-and-assign greedy minimising a weighted sum via the rearrangement inequality.
- Jim and the OrdersSort by a derived key, then read off the order — another sort-first greedy.
- Minimum number of arrows to burst balloonsComing soonSame fixed/variable interval covering idea — sort by end and greedily cover.
- Permuting Two ArraysSorting unlocks a per-index greedy check, like the per-window check here.