Fair Distribution of Cookies
Problem
cookies[i] is the number of cookies in bag i. Hand every
bag, unopened, to one of k children. The unfairness of a split is the
largest total any child receives; return the minimum possible unfairness.[8,15,10,20,8], k = 2Output31 (8+15+8 | 10+20)[6,1,3,2,2,4,1,2], k = 3Output72 ≤ n ≤ 8 · 1 ≤ cookies[i] ≤ 105 · 2 ≤ k ≤ n
Try it yourself
class Solution {
public int distributeCookies(int[] cookies, int k) {
}
}
Instinct n is tiny (about 20 or less) and you must use, visit or assign every item - make the set of used items a bitmask and DP over all 2^n subsets.
Memory hack Small n, big choice - let the bits keep score: each item is a light switch and the mask is the whole switch panel.
Open the Bitmask DP guide →Approaches
Brute ForceTry Every Assignment
O(kn)Space O(n + k)Give bag i to each child in turn and recurse, carrying the running
maximum. At the end, the running max is that split's unfairness; keep the smallest.
The recursion tree has kn leaves.
private int best = Integer.MAX_VALUE; public int distributeCookies(int[] cookies, int k) { dfs(cookies, new int[k], 0, 0); return best; } private void dfs(int[] cookies, int[] bins, int i, int curMax) { if (i == cookies.length) { best = Math.min(best, curMax); return; } for (int c = 0; c < bins.length; c++) { bins[c] += cookies[i]; dfs(cookies, bins, i + 1, Math.max(curMax, bins[c])); bins[c] -= cookies[i]; } }
Simple and correct, but explores symmetric and hopeless branches.
BetterBacktracking + Pruning + Symmetry
O(kn) worstSpace O(n + k)Three cuts: hand out big bags first (bad branches fail early), skip a
child if their total would reach best, and try only the first empty
child — empty children are interchangeable, so the others repeat the same
split.
private int best = Integer.MAX_VALUE; public int distributeCookies(int[] cookies, int k) { int[] c = cookies.clone(); Arrays.sort(c); // assign from the largest bag down dfs(c, new int[k], c.length - 1, 0); return best; } private void dfs(int[] c, int[] bins, int i, int curMax) { if (i < 0) { best = curMax; // pruning guarantees curMax < best return; } for (int j = 0; j < bins.length; j++) { if (bins[j] + c[i] < best) { bins[j] += c[i]; dfs(c, bins, i - 1, Math.max(curMax, bins[j])); bins[j] -= c[i]; } if (bins[j] == 0) break; // other empty children are symmetric } }
Same worst-case bound, but typically orders of magnitude fewer nodes.
OptimalBitmask DP over Subsets
O(k·3n)Space O(2n)Precompute sum[mask]. Let dp[mask] = best unfairness
giving exactly the bags in mask to the children so far. Adding one child
who takes submask sub:
max(sum[sub], dp[mask ^ sub]). Enumerating all submasks of all masks
costs 3n per child.
public int distributeCookies(int[] cookies, int k) { int n = cookies.length, full = (1 << n) - 1; int[] sum = new int[1 << n]; for (int mask = 1; mask <= full; mask++) { int low = Integer.numberOfTrailingZeros(mask); sum[mask] = sum[mask & (mask - 1)] + cookies[low]; } int[] dp = sum.clone(); // one child takes everything in mask for (int child = 2; child <= k; child++) { int[] next = new int[1 << n]; for (int mask = 0; mask <= full; mask++) { int best = dp[mask]; // new child gets nothing for (int sub = mask; sub > 0; sub = (sub - 1) & mask) { best = Math.min(best, Math.max(sum[sub], dp[mask ^ sub])); } next[mask] = best; } dp = next; } return dp[full]; }
Guaranteed polynomial in k: 8·38 ≈ 52K steps vs 88 ≈ 16.7M.
sub = (sub - 1) & mask walks every submask of
mask in decreasing order — memorise this idiom.At a glance
| Level | Approach | Time | Space |
|---|---|---|---|
| Brute Force | Try Every Assignment | O(kn) | O(n + k) |
| Better | Backtracking + Pruning + Symmetry | O(kn) worst | O(n + k) |
| Optimal | Bitmask DP over Subsets | O(k·3n) | O(2n) |
- Greedy “give the bag to the poorest child” is not optimal (e.g. [3,3,2,2,2], k=2).
- Breaking on an empty child before trying it — must try the first empty one once.
- Recomputing the max over all k children at every leaf — carry
curMaxinstead.
- Read the constraints aloud: n ≤ 8 invites exponential search.
- Explain the symmetry-breaking rule; it is the biggest practical speed-up.
- Offer the bitmask DP for a guaranteed bound independent of kn.