Facebook Pixel

2305. Fair Distribution of Cookies

Problem Description

You have an integer array cookies where cookies[i] represents the number of cookies in the i-th bag. You need to distribute all these bags to k children with the following constraints:

  • Each bag must be given to exactly one child (you cannot split a bag between multiple children)
  • All cookies in a bag stay together

The unfairness of a distribution is defined as the maximum total number of cookies that any single child receives.

Your task is to find the minimum possible unfairness across all valid distributions.

For example, if you have cookies = [8, 15, 10, 20, 8] and k = 2 children:

  • One possible distribution: Child 1 gets bags with [8, 15, 8] (total = 31 cookies), Child 2 gets bags with [10, 20] (total = 30 cookies). The unfairness is max(31, 30) = 31.
  • Another distribution: Child 1 gets bags with [20, 8] (total = 28 cookies), Child 2 gets bags with [8, 15, 10] (total = 33 cookies). The unfairness is max(28, 33) = 33.

The goal is to find the distribution strategy that minimizes this maximum value (the unfairness).

Flowchart Walkthrough

First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:

Is it a graph?

  • No: The problem doesn't involve nodes and edges or graph traversal. We're dealing with distributing bags of cookies to children.

Need to solve for kth smallest/largest?

  • No: We're not looking for a kth smallest or largest element. We need to find the minimum possible unfairness value.

Involves Linked Lists?

  • No: The problem uses an array of cookies, not linked lists.

Does the problem have small constraints?

  • Yes: Looking at the problem, we need to distribute bags to k children, and typically these distribution problems have relatively small constraints (usually k ≤ 10 and array length ≤ 15-20). We need to explore all possible ways to distribute the bags.

Brute force / Backtracking?

  • Yes: Since we have small constraints and need to explore all possible distributions to find the optimal one, backtracking is the appropriate approach. We need to:
    • Try assigning each bag to each possible child
    • Track the current distribution's unfairness
    • Backtrack when we've assigned all bags or when a distribution exceeds our current best
    • Explore all possibilities to find the minimum unfairness

Conclusion: The flowchart correctly leads us to use a Backtracking approach. This makes sense because we need to exhaustively search through all possible ways to distribute the cookie bags among the children, keeping track of the minimum unfairness found, and pruning branches that cannot lead to better solutions.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

When we need to distribute items among multiple recipients and find the optimal distribution, we're essentially dealing with a decision tree where at each step we decide which recipient gets which item. Since we want to minimize the maximum cookies any child receives, we need to explore different distribution strategies.

The key insight is that this is a classic assignment problem with a twist - we care about minimizing the maximum load rather than the total. Think of it like distributing work among employees where you want to ensure no one is overworked. We can't use a greedy approach (like always giving to the child with the least cookies) because local optimal choices don't guarantee a global optimum.

For example, if we have cookies [20, 15, 10] and 2 children, greedily giving the largest bag first might not lead to the best solution. We need to try different combinations to find the truly optimal distribution.

Since we must try all possible distributions to guarantee finding the minimum unfairness, backtracking becomes our natural choice. We systematically:

  1. Take a bag of cookies
  2. Try giving it to each child
  3. Recursively distribute the remaining bags
  4. Track the maximum cookies any child gets in each complete distribution
  5. Backtrack to try other possibilities

To make this efficient, we can add pruning strategies:

  • Sort cookies in descending order to fail fast (larger values are more likely to exceed our current best answer early)
  • Skip distributions where a child already has more cookies than our current best answer
  • Skip giving cookies to a child if the previous child has the same amount (avoids duplicate distributions)

This exhaustive search with intelligent pruning ensures we find the minimum possible unfairness while avoiding unnecessary computation.

Learn more about Dynamic Programming, Backtracking and Bitmask patterns.

Solution Approach

The implementation uses a depth-first search (DFS) with backtracking to explore all possible distributions of cookie bags to children.

Data Structures:

  • cnt: An array of size k that tracks the current number of cookies each child has
  • ans: A variable to store the minimum unfairness found so far, initialized to infinity

Algorithm Steps:

  1. Preprocessing: Sort the cookies array in descending order. This optimization helps with pruning - when we assign larger cookie bags first, we can more quickly identify and skip branches that will exceed our current best answer.

  2. DFS Function: The recursive dfs(i) function handles distributing the i-th bag of cookies:

    • Base Case: When i >= len(cookies), all bags have been distributed. Calculate the current unfairness as max(cnt) and update ans if this distribution is better.

    • Recursive Case: For the current bag i, try giving it to each child j:

      Pruning conditions (skip if):

      • cnt[j] + cookies[i] >= ans: Adding this bag to child j would already meet or exceed our current best unfairness, so no point exploring this branch
      • j > 0 and cnt[j] == cnt[j-1]: If the current child has the same number of cookies as the previous child, giving the bag to either produces equivalent distributions (avoids duplicates)

      Backtracking steps:

      • Add cookies[i] to child j's count: cnt[j] += cookies[i]
      • Recursively distribute remaining bags: dfs(i + 1)
      • Backtrack by removing the cookies: cnt[j] -= cookies[i]
  3. Main Execution:

    • Initialize cnt with zeros and ans with infinity
    • Start the DFS from bag 0
    • Return the minimum unfairness found

The sorting optimization combined with the two pruning conditions significantly reduces the search space. The first pruning condition acts as an upper bound check, while the second eliminates symmetric distributions, making the algorithm efficient enough for the given constraints.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's trace through a small example with cookies = [6, 4, 5] and k = 2 children.

Step 1: Preprocessing

  • Sort cookies in descending order: [6, 5, 4]
  • Initialize cnt = [0, 0] (tracking cookies for each child)
  • Initialize ans = infinity

Step 2: DFS Exploration

Starting with dfs(0) - distributing bag with 6 cookies:

Branch 1: Give bag 0 (6 cookies) to child 0

  • cnt = [6, 0]

  • Call dfs(1) - distributing bag with 5 cookies

    Branch 1.1: Give bag 1 (5 cookies) to child 0

    • cnt = [11, 0]
    • Call dfs(2) - distributing bag with 4 cookies
      • Must give to child 1 (child 0 already has 11)
      • cnt = [11, 4]
      • All bags distributed: unfairness = max(11, 4) = 11
      • Update ans = 11
      • Backtrack: cnt = [11, 0]
    • Backtrack: cnt = [6, 0]

    Branch 1.2: Give bag 1 (5 cookies) to child 1

    • cnt = [6, 5]

    • Call dfs(2) - distributing bag with 4 cookies

      Branch 1.2.1: Give to child 0

      • cnt = [10, 5]
      • All bags distributed: unfairness = max(10, 5) = 10
      • Update ans = 10 (better than 11)
      • Backtrack: cnt = [6, 5]

      Branch 1.2.2: Give to child 1

      • cnt = [6, 9]
      • All bags distributed: unfairness = max(6, 9) = 9
      • Update ans = 9 (better than 10)
      • Backtrack: cnt = [6, 5]
    • Backtrack: cnt = [6, 0]

  • Backtrack: cnt = [0, 0]

Branch 2: Give bag 0 (6 cookies) to child 1

  • cnt = [0, 6]

  • Call dfs(1) - distributing bag with 5 cookies

    Branch 2.1: Give bag 1 (5 cookies) to child 0

    • cnt = [5, 6]

    • Call dfs(2) - distributing bag with 4 cookies

      Branch 2.1.1: Give to child 0

      • cnt = [9, 6]
      • Pruning: 9 >= ans (9), skip this branch
      • Backtrack: cnt = [5, 6]

      Branch 2.1.2: Give to child 1

      • cnt = [5, 10]
      • Pruning: 10 >= ans (9), skip this branch
      • Backtrack: cnt = [5, 6]
    • Backtrack: cnt = [0, 6]

    Branch 2.2: Give bag 1 (5 cookies) to child 1

    • cnt = [0, 11]
    • Pruning: 11 >= ans (9), skip entire subtree
    • Backtrack: cnt = [0, 6]
  • Backtrack: cnt = [0, 0]

Result: The minimum unfairness is 9, achieved when:

  • Child 0 gets bag with 6 cookies
  • Child 1 gets bags with 5 and 4 cookies (total = 9)

The pruning conditions helped us skip several branches:

  • When a child's current total would exceed our best answer
  • This reduced our search from exploring all 8 possible distributions to examining only the necessary ones

Solution Implementation

1class Solution:
2    def distributeCookies(self, cookies: List[int], k: int) -> int:
3        # Initialize the minimum unfairness to infinity
4        self.min_unfairness = float('inf')
5      
6        # Array to track the total cookies each child has
7        children_cookies = [0] * k
8      
9        # Sort cookies in descending order for better pruning
10        cookies.sort(reverse=True)
11      
12        def backtrack(cookie_index: int) -> None:
13            """
14            Recursively distribute cookies using backtracking.
15          
16            Args:
17                cookie_index: Current index of cookie being distributed
18            """
19            # Base case: all cookies have been distributed
20            if cookie_index >= len(cookies):
21                # Calculate unfairness (maximum cookies any child has)
22                current_unfairness = max(children_cookies)
23                self.min_unfairness = min(self.min_unfairness, current_unfairness)
24                return
25          
26            # Try giving current cookie to each child
27            for child in range(k):
28                # Pruning condition 1: Skip if giving this cookie would exceed current best
29                if children_cookies[child] + cookies[cookie_index] >= self.min_unfairness:
30                    continue
31              
32                # Pruning condition 2: Skip duplicate states (consecutive children with same amount)
33                if child > 0 and children_cookies[child] == children_cookies[child - 1]:
34                    continue
35              
36                # Give cookie to current child
37                children_cookies[child] += cookies[cookie_index]
38              
39                # Recursively distribute remaining cookies
40                backtrack(cookie_index + 1)
41              
42                # Backtrack: take cookie back from current child
43                children_cookies[child] -= cookies[cookie_index]
44      
45        # Start the backtracking process
46        backtrack(0)
47      
48        return self.min_unfairness
49
1class Solution {
2    private int[] cookies;           // Array of cookie bags to distribute
3    private int[] childrenLoad;      // Current cookie count for each child
4    private int k;                    // Number of children
5    private int n;                    // Number of cookie bags
6    private int minUnfairness = 1 << 30;  // Minimum unfairness found (initialized to large value)
7
8    public int distributeCookies(int[] cookies, int k) {
9        this.n = cookies.length;
10        this.childrenLoad = new int[k];
11      
12        // Sort cookies in ascending order for optimization
13        Arrays.sort(cookies);
14        this.cookies = cookies;
15        this.k = k;
16      
17        // Start DFS from the largest cookie (index n-1) to smallest (index 0)
18        // This helps with pruning as larger values have more impact
19        dfs(n - 1);
20      
21        return minUnfairness;
22    }
23
24    private void dfs(int cookieIndex) {
25        // Base case: all cookies have been distributed
26        if (cookieIndex < 0) {
27            // Calculate the maximum cookies any child has (unfairness)
28            int maxLoad = 0;
29            for (int load : childrenLoad) {
30                maxLoad = Math.max(maxLoad, load);
31            }
32            minUnfairness = Math.min(minUnfairness, maxLoad);
33            return;
34        }
35      
36        // Try giving current cookie to each child
37        for (int childIndex = 0; childIndex < k; childIndex++) {
38            // Pruning condition 1: Skip if giving this cookie would exceed current best unfairness
39            if (childrenLoad[childIndex] + cookies[cookieIndex] >= minUnfairness) {
40                continue;
41            }
42          
43            // Pruning condition 2: Skip if current child has same load as previous child
44            // (avoids redundant permutations since children are indistinguishable)
45            if (childIndex > 0 && childrenLoad[childIndex] == childrenLoad[childIndex - 1]) {
46                continue;
47            }
48          
49            // Give cookie to current child
50            childrenLoad[childIndex] += cookies[cookieIndex];
51          
52            // Recursively distribute remaining cookies
53            dfs(cookieIndex - 1);
54          
55            // Backtrack: remove cookie from current child
56            childrenLoad[childIndex] -= cookies[cookieIndex];
57        }
58    }
59}
60
1class Solution {
2public:
3    int distributeCookies(vector<int>& cookies, int k) {
4        // Sort cookies in descending order for better pruning
5        sort(cookies.rbegin(), cookies.rend());
6      
7        // Array to track the total cookies each child receives
8        int childrenCookies[k];
9        memset(childrenCookies, 0, sizeof(childrenCookies));
10      
11        int numCookies = cookies.size();
12        int minUnfairness = INT_MAX;
13      
14        // DFS function to try all possible distributions
15        function<void(int)> dfs = [&](int cookieIndex) {
16            // Base case: all cookies have been distributed
17            if (cookieIndex >= numCookies) {
18                // Find the maximum cookies any child has (unfairness)
19                int currentUnfairness = *max_element(childrenCookies, childrenCookies + k);
20                minUnfairness = min(minUnfairness, currentUnfairness);
21                return;
22            }
23          
24            // Try giving current cookie to each child
25            for (int childIndex = 0; childIndex < k; ++childIndex) {
26                // Pruning condition 1: Skip if current distribution already exceeds minimum unfairness
27                if (childrenCookies[childIndex] + cookies[cookieIndex] >= minUnfairness) {
28                    continue;
29                }
30              
31                // Pruning condition 2: Skip duplicate states (if previous child has same cookies)
32                if (childIndex > 0 && childrenCookies[childIndex] == childrenCookies[childIndex - 1]) {
33                    continue;
34                }
35              
36                // Give cookie to current child
37                childrenCookies[childIndex] += cookies[cookieIndex];
38              
39                // Recursively distribute remaining cookies
40                dfs(cookieIndex + 1);
41              
42                // Backtrack: take cookie back from current child
43                childrenCookies[childIndex] -= cookies[cookieIndex];
44            }
45        };
46      
47        // Start the DFS from the first cookie
48        dfs(0);
49      
50        return minUnfairness;
51    }
52};
53
1/**
2 * Distributes cookies among k children to minimize the maximum total cookies any child receives
3 * @param cookies - Array of cookie bags where cookies[i] represents the number of cookies in the i-th bag
4 * @param k - Number of children to distribute cookies to
5 * @returns The minimum possible maximum total cookies any child can receive
6 */
7function distributeCookies(cookies: number[], k: number): number {
8    // Array to track the current distribution of cookies to each child
9    const childCookieCounts: number[] = new Array(k).fill(0);
10  
11    // Initialize answer with a large value (2^30)
12    let minMaxCookies: number = 1 << 30;
13  
14    /**
15     * Depth-first search to try all possible distributions
16     * @param cookieIndex - Current index of the cookie bag being distributed
17     */
18    const distributeRecursively = (cookieIndex: number): void => {
19        // Base case: all cookies have been distributed
20        if (cookieIndex >= cookies.length) {
21            // Update the answer with the maximum cookies any child has
22            minMaxCookies = Math.min(minMaxCookies, Math.max(...childCookieCounts));
23            return;
24        }
25      
26        // Try giving the current cookie bag to each child
27        for (let childIndex = 0; childIndex < k; childIndex++) {
28            // Pruning condition 1: Skip if giving this bag would exceed current best answer
29            // Pruning condition 2: Skip duplicate states (when previous child has same count)
30            if (childCookieCounts[childIndex] + cookies[cookieIndex] >= minMaxCookies || 
31                (childIndex > 0 && childCookieCounts[childIndex] === childCookieCounts[childIndex - 1])) {
32                continue;
33            }
34          
35            // Give current cookie bag to this child
36            childCookieCounts[childIndex] += cookies[cookieIndex];
37          
38            // Recursively distribute remaining cookies
39            distributeRecursively(cookieIndex + 1);
40          
41            // Backtrack: remove the cookie bag from this child
42            childCookieCounts[childIndex] -= cookies[cookieIndex];
43        }
44    };
45  
46    // Start the distribution process from the first cookie bag
47    distributeRecursively(0);
48  
49    return minMaxCookies;
50}
51

Time and Space Complexity

Time Complexity: O(k^n) where n is the number of cookies and k is the number of children.

The algorithm uses backtracking to try all possible distributions of cookies among k children. For each cookie (n total cookies), we have at most k choices of which child to give it to. This creates a recursion tree with:

  • Depth: n (one level for each cookie)
  • Branching factor: at most k at each level

Although there are pruning conditions (cnt[j] + cookies[i] >= ans and cnt[j] == cnt[j - 1]) that reduce the actual number of branches explored, the worst-case scenario still requires exploring k^n possibilities.

Space Complexity: O(n + k)

The space complexity consists of:

  • Recursion call stack: O(n) - The maximum depth of recursion is n (the number of cookies)
  • Array cnt: O(k) - Stores the current distribution of cookies for k children
  • Sorting the cookies array is done in-place, so no additional space for that
  • Variable ans: O(1)

Therefore, the total space complexity is O(n + k).

Learn more about how to find time and space complexity quickly.

Common Pitfalls

1. Missing Early Termination for Empty Children

The Pitfall: The current pruning condition if child > 0 and children_cookies[child] == children_cookies[child - 1] only avoids duplicate states between consecutive children with the same cookie count. However, when multiple children have 0 cookies (which is common early in the recursion), the algorithm still explores giving the current bag to each of these empty children separately, even though these distributions are equivalent.

For example, with 3 children all having 0 cookies, the algorithm tries:

  • Give bag to child 0 (explores full subtree)
  • Give bag to child 1 (explores same subtree again)
  • Give bag to child 2 (explores same subtree yet again)

The Solution: Add an early termination when encountering an empty child. Once we've explored giving a bag to one empty child, we don't need to try other empty children:

for child in range(k):
    # Pruning condition 1: Skip if exceeds current best
    if children_cookies[child] + cookies[cookie_index] >= self.min_unfairness:
        continue
  
    # Pruning condition 2: Skip duplicate states
    if child > 0 and children_cookies[child] == children_cookies[child - 1]:
        continue
  
    # Give cookie to current child
    children_cookies[child] += cookies[cookie_index]
  
    # Recursively distribute remaining cookies
    backtrack(cookie_index + 1)
  
    # Backtrack
    children_cookies[child] -= cookies[cookie_index]
  
    # IMPORTANT: If this child had 0 cookies, no need to try other empty children
    if children_cookies[child] == 0:
        break

2. Integer Overflow with Large Cookie Values

The Pitfall: While Python handles arbitrary precision integers automatically, in other languages or when optimizing with NumPy arrays, the sum of cookies could overflow standard integer types if cookie values are large.

The Solution: Ensure appropriate data types are used for tracking cookie counts, or add validation for input constraints:

# Add input validation if needed
if any(c > 10**9 for c in cookies) or len(cookies) * max(cookies) > 10**18:
    # Handle potential overflow scenarios
    pass

3. Not Handling Edge Case: k > len(cookies)

The Pitfall: When there are more children than cookie bags, some children will inevitably get 0 cookies. The current solution handles this correctly, but it's worth noting that the minimum unfairness in this case is always 0 (since at least one child gets nothing, and we can give all bags to one child).

The Solution: Add an optimization for this edge case:

def distributeCookies(self, cookies: List[int], k: int) -> int:
    # Edge case: more children than bags
    if k > len(cookies):
        return max(cookies)  # Best we can do is give one bag to one child
  
    # Rest of the implementation...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following uses divide and conquer strategy?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More