LeetCode 2305 - Fair Distribution of Cookies
The problem asks us to distribute a set of cookie bags among k children such that the unfairness of the distribution is minimized. Here, unfairness is defined as the maximum total number of cookies any single child receives.
Difficulty: 🟡 Medium
Topics: Array, Dynamic Programming, Backtracking, Bit Manipulation, Bitmask
Solution
Problem Understanding
The problem asks us to distribute a set of cookie bags among k children such that the unfairness of the distribution is minimized. Here, unfairness is defined as the maximum total number of cookies any single child receives.
The input consists of an array cookies where cookies[i] represents the number of cookies in the i-th bag, and an integer k representing the number of children. Each cookie bag must be assigned entirely to a single child, so splitting is not allowed. The goal is to compute the minimum possible value of the maximum sum of cookies received by any child after distributing all bags.
Constraints indicate that cookies.length is small (at most 8), which suggests that an exhaustive or combinatorial approach is feasible. Each cookie bag can contain up to 10^5 cookies, and k can be as small as 2 or as large as the number of cookie bags.
Important edge cases include situations where the number of children equals the number of bags, where one child may end up with multiple high-value bags, or where all bags have equal cookies.
Approaches
A brute-force approach is to consider every possible assignment of each cookie bag to one of the k children. Since each bag can go to any child independently, there are k^n possible assignments, where n is the number of cookie bags. For each assignment, we compute the total cookies each child receives and track the maximum. The minimum of all these maxima is the answer. While correct, this approach grows exponentially and becomes impractical for larger n (though n <= 8 keeps it manageable here).
The key insight for optimization is backtracking with pruning. We recursively assign bags to children while maintaining an array of current cookie totals per child. If at any point the current maximum exceeds a known minimum unfairness, we can prune that branch since continuing cannot improve the result. This avoids exploring all k^n combinations in practice.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(k^n * n) | O(n) | Try all assignments of bags to children, calculate max unfairness each time |
| Optimal (Backtracking with Pruning) | O(k^n) in worst case, faster in practice | O(k + n) | Recursively assign bags with pruning based on current maximum |
Algorithm Walkthrough
- Initialize an array
totalsof lengthkwith zeros to track the current sum of cookies for each child. - Define a recursive function
dfs(index)whereindexrepresents the current cookie bag to assign. - If
index == n, all bags are assigned. Compute the maximum value intotalsand update the global minimum unfairness if it is smaller. - For each child
ifrom 0 tok-1, assigncookies[index]to childiand updatetotals[i]. - Before recursing, check if
totals[i]is already greater than or equal to the current minimum unfairness. If so, skip this branch. - Recurse to
dfs(index + 1). - After recursion, backtrack by removing
cookies[index]fromtotals[i]. - Continue this for all children, exploring all feasible distributions.
Why it works: The backtracking approach explores all feasible distributions while pruning paths that cannot improve the solution. This ensures we find the minimum unfairness. Each leaf of the recursion tree corresponds to a complete assignment of all cookie bags.
Python Solution
from typing import List
class Solution:
def distributeCookies(self, cookies: List[int], k: int) -> int:
n = len(cookies)
totals = [0] * k
self.min_unfairness = float('inf')
def dfs(index: int):
if index == n:
self.min_unfairness = min(self.min_unfairness, max(totals))
return
for i in range(k):
totals[i] += cookies[index]
if totals[i] < self.min_unfairness:
dfs(index + 1)
totals[i] -= cookies[index]
if totals[i] == 0:
break
dfs(0)
return self.min_unfairness
This Python solution uses a recursive depth-first search. The totals array keeps track of the sum of cookies each child has. The if totals[i] == 0: break line is an optimization that avoids redundant symmetric assignments where multiple children have zero cookies.
Go Solution
func distributeCookies(cookies []int, k int) int {
n := len(cookies)
totals := make([]int, k)
minUnfairness := 1 << 60
var dfs func(int)
dfs = func(index int) {
if index == n {
maxTotal := 0
for _, t := range totals {
if t > maxTotal {
maxTotal = t
}
}
if maxTotal < minUnfairness {
minUnfairness = maxTotal
}
return
}
for i := 0; i < k; i++ {
totals[i] += cookies[index]
if totals[i] < minUnfairness {
dfs(index + 1)
}
totals[i] -= cookies[index]
if totals[i] == 0 {
break
}
}
}
dfs(0)
return minUnfairness
}
In Go, the main differences are the use of slices for totals and integer constants for infinity. The recursion logic is identical to Python.
Worked Examples
Example 1: cookies = [8,15,10,20,8], k = 2
| Index | totals before | assign bag | totals after | max(totals) |
|---|---|---|---|---|
| 0 | [0,0] | 8 -> child 0 | [8,0] | 8 |
| 1 | [8,0] | 15 -> child 1 | [8,15] | 15 |
| 2 | [8,15] | 10 -> child 0 | [18,15] | 18 |
| 3 | [18,15] | 20 -> child 1 | [18,35] | 35 (pruned) |
| ... | ... | ... | ... | ... |
| Final | [31,30] | - | - | 31 |
Example 2: cookies = [6,1,3,2,2,4,1,2], k = 3
The algorithm will explore assignments but prune branches exceeding current minimum. The optimal distribution is [6,1], [3,2,2], [4,1,2] resulting in max total 7.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(k^n) | In the worst case, each of n bags can go to any of k children |
| Space | O(k + n) | O(k) for totals array, O(n) for recursion stack |
Although the theoretical time complexity is exponential, pruning reduces actual computation for typical inputs.
Test Cases
# Provided examples
assert Solution().distributeCookies([8,15,10,20,8], 2) == 31 # example 1
assert Solution().distributeCookies([6,1,3,2,2,4,1,2], 3) == 7 # example 2
# Edge cases
assert Solution().distributeCookies([1,1], 2) == 1 # smallest input
assert Solution().distributeCookies([5,5,5,5], 4) == 5 # one bag per child
assert Solution().distributeCookies([10,20,30], 2) == 30 # uneven distribution
assert Solution().distributeCookies([1,2,3,4,5,6,7,8], 2) == 20 # maximal length
| Test | Why |
|---|---|
| [8,15,10,20,8], k=2 | Basic scenario from example 1 |
| [6,1,3,2,2,4,1,2], k=3 | Basic scenario from example 2 |
| [1,1], k=2 | Minimal input |
| [5,5,5,5], k=4 | Each child gets one bag, checks trivial distribution |
| [10,20,30], k=2 | Uneven distribution forces max sum selection |
| [1,2,3,4,5,6,7,8], k=2 | Maximal number of bags tests recursion depth |
Edge Cases
One important edge case is when the number of children equals the number of cookie bags. Each child receives exactly one bag, so the minimum unfairness is the maximum value in the cookies array. The algorithm handles this naturally since each leaf in the recursion tree will assign one bag per child.
Another edge case is when one child might need to take multiple high-value bags to balance the unfairness. Without pruning, the naive approach would explore all combinations unnecessarily. The implemented pruning ensures that branches where the current maximum exceeds the known minimum are skipped.
A third edge case is when several cookie bags have the same value. This can introduce symmetry in assignments. The optimization that breaks the loop when `