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.
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) andr = 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 tocnt >= k+1
sincecnt
is an integer
Algorithm Flow:
- For each candidate sweetness
mid
, we greedily try to form pieces from left to right - Each piece is formed as soon as its total sweetness reaches
mid
- If we can form more than
k
pieces (i.e., at leastk+1
pieces), thenmid
is achievable - 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 EvaluatorExample 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
- Traverse array:
- 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
- Traverse:
- 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
- Traverse:
- 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
- Traverse:
- Result:
l = 5
Step 3: Convergence
- Now
l = 5
andr = 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.
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
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!