Facebook Pixel

1231. Divide Chocolate 🔒

Problem Description

You have a chocolate bar made up of chunks, where each chunk has a sweetness value given in an array sweetness.

You want to share this chocolate with k friends. To do this, you need to make k cuts to divide the chocolate into k + 1 pieces. Each piece must consist of consecutive chunks (you can't rearrange the chunks).

After dividing the chocolate:

  • You will eat the piece with the minimum total sweetness
  • Your friends get the remaining pieces

Your goal is to maximize the total sweetness of the piece you eat by choosing where to make the cuts optimally.

For example, if sweetness = [1, 2, 3, 4, 5, 6, 7, 8, 9] and k = 5, you need to make 5 cuts to create 6 pieces. The algorithm will try to maximize the minimum piece's sweetness. One possible division could be [1,2,3], [4,5], [6], [7], [8], [9] where the pieces have sweetness 6, 9, 6, 7, 8, and 9 respectively. You would eat the piece with minimum sweetness (6), so your goal is to find a cutting strategy that maximizes this minimum value.

The function should return the maximum possible total sweetness of the piece you can get.

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

Intuition

The key insight is recognizing that this problem has a monotonic property: if we can achieve a minimum piece sweetness of x, then we can definitely achieve any minimum piece sweetness less than x. Why? Because if we can divide the chocolate such that every piece has at least sweetness x, we could make the same cuts (or adjust them slightly) to ensure every piece has at least sweetness x-1.

This monotonic property immediately suggests binary search on the answer. Instead of trying to figure out where to cut directly, we can ask: "What's the maximum sweetness x such that we can divide the chocolate into k+1 pieces where each piece has at least sweetness x?"

The search space for our answer is between 0 (worst case, we get nothing) and sum(sweetness) (best case, we get the entire chocolate bar - only possible when k=0).

For any candidate value mid in our binary search, we need to check: "Can we make k cuts such that we get k+1 pieces, each with sweetness at least mid?"

To check this feasibility, we use a greedy approach: traverse the chocolate bar from left to right, accumulating sweetness. Whenever our accumulated sweetness reaches or exceeds mid, we "make a cut" (form a piece) and reset our accumulator. If we can form more than k pieces this way, it means we can definitely achieve a minimum sweetness of mid (we have extra pieces to spare). If we can't form enough pieces, then mid is too ambitious.

The greedy approach works because if there's any way to divide the chocolate to achieve minimum sweetness mid, then greedily taking pieces as soon as they reach mid will also work. There's no benefit to making pieces larger than necessary when we're trying to maximize the count of valid pieces.

Learn more about Binary Search patterns.

Solution Approach

The solution uses binary search combined with a greedy validation function.

Binary Search Setup:

  • Initialize the search range: l = 0 (minimum possible sweetness) and r = sum(sweetness) (maximum possible sweetness if we take everything)
  • We search for the maximum value that satisfies our condition, so we use the binary search template that finds the rightmost valid position

Binary Search Logic:

while l < r:
    mid = (l + r + 1) >> 1  # equivalent to (l + r + 1) // 2
    if check(mid):
        l = mid  # mid is valid, try for a larger value
    else:
        r = mid - 1  # mid is too large, search smaller values

The (l + r + 1) >> 1 ensures we round up when calculating mid to avoid infinite loops when l and r are adjacent.

Greedy Validation Function (check):

The check(x) function determines if we can divide the chocolate such that each piece has at least sweetness x:

def check(x: int) -> bool:
    s = cnt = 0  # s: current piece sweetness, cnt: number of pieces formed
    for v in sweetness:
        s += v  # accumulate sweetness
        if s >= x:  # can form a piece with at least sweetness x
            s = 0  # reset for next piece
            cnt += 1  # increment piece count
    return cnt > k  # need k+1 pieces (more than k)

Why cnt > k instead of cnt >= k+1?

  • We need k+1 total pieces (one for us, k for friends)
  • The condition cnt > k is equivalent to cnt >= k+1 since cnt is an integer

Algorithm Flow:

  1. For each candidate sweetness mid, we greedily try to form pieces from left to right
  2. Each piece is formed as soon as its total sweetness reaches mid
  3. If we can form more than k pieces (i.e., at least k+1 pieces), then mid is achievable
  4. Binary search converges to the maximum achievable minimum sweetness

Time Complexity: O(n * log(sum(sweetness))) where n is the length of the sweetness array. We perform binary search over the range [0, sum(sweetness)], and each validation takes O(n) time.

Space Complexity: O(1) as we only use a constant amount of extra space.

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 walk through a small example with sweetness = [1, 2, 2, 1, 2, 2, 1, 2, 2] and k = 2 (we need to make 2 cuts to create 3 pieces).

Step 1: Initialize Binary Search Range

  • l = 0 (minimum possible sweetness)
  • r = sum([1, 2, 2, 1, 2, 2, 1, 2, 2]) = 15 (maximum possible sweetness)

Step 2: Binary Search Process

Iteration 1:

  • mid = (0 + 15 + 1) // 2 = 8
  • Check if we can achieve minimum sweetness of 8:
    • Traverse array: [1, 2, 2, 1, 2, 2, 1, 2, 2]
    • Accumulate: 1 → 3 → 5 → 6 → 8 (≥8, form piece 1, reset)
    • Continue: 2 → 3 → 5 → 7 (can't reach 8, end of array)
    • Pieces formed: 1 (need 3)
    • Return false since 1 ≤ 2
  • Result: r = 8 - 1 = 7

Iteration 2:

  • mid = (0 + 7 + 1) // 2 = 4
  • Check if we can achieve minimum sweetness of 4:
    • Traverse: [1, 2, 2, 1, 2, 2, 1, 2, 2]
    • Accumulate: 1 → 3 → 5 (≥4, form piece 1, reset)
    • Continue: 1 → 3 → 5 (≥4, form piece 2, reset)
    • Continue: 1 → 3 → 5 (≥4, form piece 3, reset)
    • Pieces formed: 3 (need 3)
    • Return true since 3 > 2
  • Result: l = 4

Iteration 3:

  • mid = (4 + 7 + 1) // 2 = 6
  • Check if we can achieve minimum sweetness of 6:
    • Traverse: [1, 2, 2, 1, 2, 2, 1, 2, 2]
    • Accumulate: 1 → 3 → 5 → 6 (≥6, form piece 1, reset)
    • Continue: 2 → 4 → 5 → 7 (≥6, form piece 2, reset)
    • Continue: 2 (can't reach 6, end of array)
    • Pieces formed: 2 (need 3)
    • Return false since 2 ≤ 2
  • Result: r = 6 - 1 = 5

Iteration 4:

  • mid = (4 + 5 + 1) // 2 = 5
  • Check if we can achieve minimum sweetness of 5:
    • Traverse: [1, 2, 2, 1, 2, 2, 1, 2, 2]
    • Accumulate: 1 → 3 → 5 (≥5, form piece 1, reset)
    • Continue: 1 → 3 → 5 (≥5, form piece 2, reset)
    • Continue: 1 → 3 → 5 (≥5, form piece 3, reset)
    • Pieces formed: 3 (need 3)
    • Return true since 3 > 2
  • Result: l = 5

Step 3: Convergence

  • Now l = 5 and r = 5, so the loop exits
  • Return l = 5

Final Answer: 5

The optimal division would be: [1, 2, 2] (sweetness = 5), [1, 2, 2] (sweetness = 5), [1, 2, 2] (sweetness = 5). You eat the piece with minimum sweetness, which is 5.

Solution Implementation

1class Solution:
2    def maximizeSweetness(self, sweetness: List[int], k: int) -> int:
3        """
4        Find the maximum minimum sweetness when dividing chocolate into k+1 pieces.
5      
6        Args:
7            sweetness: List of sweetness values for each chocolate chunk
8            k: Number of cuts to make (resulting in k+1 pieces)
9      
10        Returns:
11            Maximum possible minimum sweetness value
12        """
13      
14        def can_divide_with_min_sweetness(min_sweetness: int) -> bool:
15            """
16            Check if we can divide the chocolate into at least k+1 pieces
17            where each piece has sweetness >= min_sweetness.
18          
19            Args:
20                min_sweetness: Minimum sweetness threshold for each piece
21          
22            Returns:
23                True if we can make more than k cuts (k+1 pieces) with given threshold
24            """
25            current_sum = 0
26            pieces_count = 0
27          
28            # Greedily form pieces: take chunks until sum >= min_sweetness
29            for chunk_sweetness in sweetness:
30                current_sum += chunk_sweetness
31              
32                # If current piece meets minimum requirement, cut here
33                if current_sum >= min_sweetness:
34                    current_sum = 0  # Start new piece
35                    pieces_count += 1
36          
37            # We need at least k+1 pieces (more than k cuts)
38            return pieces_count > k
39      
40        # Binary search range: minimum is 0, maximum is total sweetness
41        left, right = 0, sum(sweetness)
42      
43        # Binary search for the maximum valid minimum sweetness
44        while left < right:
45            # Use upper mid to avoid infinite loop when left = right - 1
46            mid = (left + right + 1) // 2
47          
48            if can_divide_with_min_sweetness(mid):
49                # Can achieve this minimum, try for higher
50                left = mid
51            else:
52                # Cannot achieve this minimum, lower the requirement
53                right = mid - 1
54      
55        return left
56
1class Solution {
2    /**
3     * Finds the maximum minimum sweetness when dividing chocolate into k+1 pieces.
4     * Uses binary search on the answer space.
5     * 
6     * @param sweetness Array containing sweetness values of each chocolate chunk
7     * @param k Number of cuts to make (resulting in k+1 pieces)
8     * @return Maximum possible minimum sweetness among all pieces
9     */
10    public int maximizeSweetness(int[] sweetness, int k) {
11        // Initialize binary search boundaries
12        int left = 0;  // Minimum possible sweetness (could be 1 for non-empty pieces)
13        int right = 0; // Maximum possible sweetness (sum of all chunks)
14      
15        // Calculate the sum of all sweetness values as upper bound
16        for (int chunkSweetness : sweetness) {
17            right += chunkSweetness;
18        }
19      
20        // Binary search for the maximum minimum sweetness
21        while (left < right) {
22            // Calculate mid point (using +1 to avoid infinite loop when left = right - 1)
23            int mid = (left + right + 1) >> 1;
24          
25            // Check if we can achieve at least 'mid' sweetness for each piece
26            if (canAchieveMinimumSweetness(sweetness, mid, k)) {
27                // If possible, try for a higher minimum sweetness
28                left = mid;
29            } else {
30                // If not possible, reduce the target minimum sweetness
31                right = mid - 1;
32            }
33        }
34      
35        return left;
36    }
37  
38    /**
39     * Checks if it's possible to divide the chocolate such that each piece
40     * has at least 'minimumSweetness' total sweetness.
41     * 
42     * @param sweetness Array containing sweetness values
43     * @param minimumSweetness Target minimum sweetness for each piece
44     * @param k Number of cuts allowed (need k+1 pieces total)
45     * @return true if we can create k+1 pieces with at least minimumSweetness each
46     */
47    private boolean canAchieveMinimumSweetness(int[] sweetness, int minimumSweetness, int k) {
48        int currentPieceSweetness = 0;  // Running sum of current piece
49        int piecesCount = 0;             // Number of valid pieces created
50      
51        // Greedily create pieces from left to right
52        for (int chunkSweetness : sweetness) {
53            currentPieceSweetness += chunkSweetness;
54          
55            // If current piece meets minimum requirement, cut here
56            if (currentPieceSweetness >= minimumSweetness) {
57                currentPieceSweetness = 0;  // Reset for next piece
58                piecesCount++;
59            }
60        }
61      
62        // Check if we can create more than k pieces (k+1 or more)
63        // We need k+1 pieces total (k cuts + 1)
64        return piecesCount > k;
65    }
66}
67
1class Solution {
2public:
3    int maximizeSweetness(vector<int>& sweetness, int k) {
4        // Binary search bounds: minimum is 0, maximum is total sum
5        int left = 0;
6        int right = accumulate(sweetness.begin(), sweetness.end(), 0);
7      
8        // Lambda function to check if we can achieve minimum sweetness of 'minSweetness'
9        // Returns true if we can make more than k cuts (k+1 pieces) with each piece >= minSweetness
10        auto canAchieveMinSweetness = [&](int minSweetness) {
11            int currentSum = 0;
12            int piecesCount = 0;
13          
14            // Greedily form pieces: take elements until sum >= minSweetness
15            for (int value : sweetness) {
16                currentSum += value;
17                if (currentSum >= minSweetness) {
18                    currentSum = 0;  // Reset for next piece
19                    ++piecesCount;   // Count this as a valid piece
20                }
21            }
22          
23            // We need at least k+1 pieces (k cuts means k+1 pieces)
24            return piecesCount > k;
25        };
26      
27        // Binary search for the maximum minimum sweetness
28        while (left < right) {
29            // Use (left + right + 1) / 2 to avoid infinite loop when left = mid
30            int mid = (left + right + 1) >> 1;
31          
32            if (canAchieveMinSweetness(mid)) {
33                // Can achieve this minimum, try for a larger value
34                left = mid;
35            } else {
36                // Cannot achieve this minimum, try smaller values
37                right = mid - 1;
38            }
39        }
40      
41        return left;
42    }
43};
44
1/**
2 * Divides a chocolate bar into k+1 pieces to maximize the minimum sweetness.
3 * Uses binary search to find the maximum possible minimum sweetness.
4 * 
5 * @param sweetness - Array of sweetness values for each chunk of chocolate
6 * @param k - Number of cuts to make (resulting in k+1 pieces)
7 * @returns The maximum possible minimum sweetness among all pieces
8 */
9function maximizeSweetness(sweetness: number[], k: number): number {
10    // Binary search bounds: minimum possible sweetness to total sweetness
11    let left: number = 0;
12    let right: number = sweetness.reduce((sum: number, value: number) => sum + value, 0);
13  
14    /**
15     * Checks if we can achieve at least k+1 pieces with minimum sweetness >= targetSweetness
16     * 
17     * @param targetSweetness - The minimum sweetness threshold to test
18     * @returns True if we can create more than k pieces with the given minimum sweetness
19     */
20    const canAchieveMinimumSweetness = (targetSweetness: number): boolean => {
21        let currentSum: number = 0;
22        let piecesCount: number = 0;
23      
24        // Greedily form pieces: create a new piece whenever sum reaches threshold
25        for (const chunkSweetness of sweetness) {
26            currentSum += chunkSweetness;
27          
28            // If current sum meets threshold, count as a piece and reset
29            if (currentSum >= targetSweetness) {
30                currentSum = 0;
31                piecesCount++;
32            }
33        }
34      
35        // Check if we can create more than k pieces (k+1 or more)
36        return piecesCount > k;
37    };
38  
39    // Binary search for the maximum achievable minimum sweetness
40    while (left < right) {
41        // Calculate mid point (using bit shift for integer division)
42        const mid: number = (left + right + 1) >> 1;
43      
44        // If we can achieve this minimum sweetness, try for a higher value
45        if (canAchieveMinimumSweetness(mid)) {
46            left = mid;
47        } else {
48            // Otherwise, reduce our target
49            right = mid - 1;
50        }
51    }
52  
53    return left;
54}
55

Time and Space Complexity

Time Complexity: O(n × log(sum(sweetness)))

The algorithm uses binary search on the answer space. The search range is from 0 to sum(sweetness), which means the binary search will perform O(log(sum(sweetness))) iterations. In each iteration, the check function is called, which iterates through all n elements of the sweetness array exactly once, taking O(n) time. Therefore, the overall time complexity is O(n × log(sum(sweetness))), where n is the length of the sweetness array.

Space Complexity: O(1)

The algorithm only uses a constant amount of extra space. The variables l, r, mid in the main function and s, cnt, v in the check function are all scalar variables that don't depend on the input size. No additional data structures are created that scale with the input, so the space complexity is O(1).

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

Common Pitfalls

1. Incorrect Binary Search Mid Calculation

Pitfall: Using mid = (left + right) // 2 instead of mid = (left + right + 1) // 2 when searching for the maximum valid value.

Why it's problematic: When left = right - 1, using (left + right) // 2 will always give left, causing an infinite loop if the condition is satisfied and we set left = mid.

Example scenario:

  • left = 5, right = 6
  • mid = (5 + 6) // 2 = 5
  • If condition is true: left = mid = 5 (no progress, infinite loop!)

Solution: Always use mid = (left + right + 1) // 2 when searching for maximum and updating with left = mid.

2. Wrong Piece Count Comparison

Pitfall: Using pieces_count >= k instead of pieces_count > k in the validation function.

Why it's problematic: We need k+1 pieces total (making k cuts). The condition pieces_count >= k would mean we only need k pieces, which is incorrect.

Solution: Use pieces_count > k (equivalent to pieces_count >= k+1).

3. Edge Case: k = 0

Pitfall: Not handling the case where k = 0 (no friends, eat the whole chocolate).

Why it's problematic: The algorithm works correctly but it's worth understanding that when k = 0, we need exactly 1 piece (no cuts), so the answer is the sum of all sweetness values.

Solution: The current algorithm handles this correctly - the validation function will only return true when we can form at least 1 piece with the given minimum sweetness.

4. Integer Overflow in Large Inputs

Pitfall: In languages with fixed integer sizes, sum(sweetness) might overflow.

Why it's problematic: If sweetness values are large and numerous, their sum might exceed integer limits.

Solution: In Python this isn't an issue due to arbitrary precision integers, but in other languages, use appropriate data types (e.g., long long in C++).

5. Misunderstanding the Greedy Strategy

Pitfall: Trying to optimize piece formation in the validation function by looking ahead or using dynamic programming.

Why it's problematic: The greedy approach (cutting as soon as we reach the threshold) is optimal for checking feasibility. More complex strategies add unnecessary complexity and don't improve the result.

Solution: Trust the greedy approach - if we can form k+1 pieces with minimum sweetness x using any strategy, we can also do it greedily.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Problem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.

Which of the following method should we use to solve this problem?


Recommended Readings

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

Load More