-
Notifications
You must be signed in to change notification settings - Fork 2
feat(algorithms, binary-search): divide chocolate #126
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
73 changes: 73 additions & 0 deletions
73
algorithms/search/binary_search/divide_chocolate/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| # Divide Chocolate | ||
|
|
||
| You have a chocolate bar made up of several chunks, and each chunk has a certain sweetness, given in an array called | ||
| sweetness. You want to share the chocolate with k friends. To do this, you’ll make k cuts to divide the bar into k + 1 | ||
| parts. Each part will consist of consecutive chunks. | ||
|
|
||
| Being a kind person, you’ll take the piece with the minimum total sweetness and give the other pieces to your friends. | ||
|
|
||
| Your task is to find the maximum total sweetness of the piece you will receive if you cut the chocolate bar optimally. | ||
|
|
||
| ## Constraints | ||
|
|
||
| - 0 <= k < sweetness.length <= 10^3 | ||
| - 1 <= sweetness[i] <= 10^3 | ||
|
|
||
| ## Solution | ||
|
|
||
| As we have a chocolate bar made of several chunks, each with its own sweetness value, dividing the chocolate evenly | ||
| won’t ensure that we get the chocolate part with the maximum possible minimum sweetness. The next natural thought for | ||
| solving this problem is to try to cut the chocolate in different ways and find out the one that gives the desired result. | ||
| But it becomes very inefficient when the chocolate bar is long. There are just too many possible combinations of where | ||
| to cut, and checking each one would take too much time. | ||
|
|
||
| So, instead of guessing exactly where to cut, we flip the problem and ask: What is the largest minimum sweetness we can | ||
| guarantee for ourselves if we divide the chocolate bar into k + 1 pieces? | ||
|
|
||
| The idea is to guess a sweetness value, called S , and check if it’s possible to cut the chocolate into k + 1 parts, | ||
| where each part has at least S sweetness. Go through the chocolate chunks one by one, adding their sweetness. When the | ||
| total reaches S or more, count it as one piece and start a new one. If it’s possible to make at least k + 1 such pieces, | ||
| then S is a good guess. If not, the guess is too high and should be lowered. For example, if S=5 , keep adding chunks | ||
| until the total is 5 or more. Count that as one piece and reset the total to 0 . Repeat this through the whole chocolate | ||
| bar. | ||
|
|
||
| The sweetness value is guessed using the binary search. The lower bound is 1, and the upper bound is the total sweetness | ||
| divided by (k + 1), as no single piece can have more than that if the chocolate is divided evenly. Start by guessing the | ||
| middle value. If it allows at least k + 1 valid parts, try a higher value. Otherwise, try a lower one. This quickly | ||
| narrows down the largest sweetness that can be guaranteed. | ||
|
|
||
| Now, let’s look at the algorithm steps below: | ||
|
|
||
| 1. Initialize the binary search range: | ||
| - Set low (the smallest possible minimum sweetness for a piece) as 1, because sweetness values are at least 1. | ||
| - Set high (the largest possible minimum sweetness) as the total sweetness divided evenly among k + 1 people, using | ||
| the formula sum(sweetness) // (k + 1). This is the best possible sweetness each person could get if the chocolate | ||
| were divided perfectly. | ||
|
|
||
| 2. Start the binary search, and while low is less than or equal to high, do the following: | ||
| - Calculate the mid-point value, mid = (low + high) // 2. This represents the current guess for the maximum minimum | ||
| sweetness we might be able to give to each person. | ||
| - Use the helper function canDivide(sweetness, k, mid) to check if dividing the chocolate into at least k + 1 pieces | ||
| is possible, where each piece has a total sweetness of at least mid. The helper works by summing chunks and counting | ||
| how many full pieces it can form with at least mid sweetness. | ||
| - If it’s possible to divide into k + 1 or more such pieces: | ||
| - That means we can afford to aim for even higher sweetness per person. So we move the lower bound up: low = mid + 1. | ||
| - We also store mid as a potential result, result. | ||
|
|
||
| 3. If it’s not possible to make enough pieces: | ||
| - Then our guess mid was too high, so we reduce the upper bound: high = mid - 1. | ||
|
|
||
| 4. After the binary search ends, return the last valid result, which is the maximum possible minimum sweetness we can | ||
| guarantee for each person. | ||
|
|
||
| ### Time Complexity | ||
|
|
||
| Let n be the total number of chunks in the chocolate bar, and S be the upper bound for our binary‐search range, i.e., | ||
| the sum of values in the sweetness list divided by k + 1. Each call to the helper function scans the entire sweetness | ||
| array once, taking O(n) time. The binary search is performed over the integer range [1…S], which takes O(log S) iterations. | ||
| Therefore, the overall time complexity of this solution is O(n×logS). | ||
|
|
||
| ### Space Complexity | ||
|
|
||
| We only use a fixed number of extra variables, and no additional arrays or data structures. Therefore the space complexity | ||
| is O(1). |
64 changes: 64 additions & 0 deletions
64
algorithms/search/binary_search/divide_chocolate/__init__.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,64 @@ | ||
| from typing import List | ||
|
|
||
|
|
||
| def maximize_sweetness(sweetness: List[int], k: int) -> int: | ||
| """ | ||
| Finds the maximum possible sweetness of the piece you will receive if you cut the chocolate bar optimally. | ||
| Args: | ||
| sweetness (List[int]): A list of sweetness values for each chunk of the chocolate bar. | ||
| k (int): The number of friends to share the chocolate bar with. | ||
| Returns: | ||
| int: The maximum possible sweetness of the piece you will receive if you cut the chocolate bar optimally. | ||
| """ | ||
| lower_bound = 1 | ||
| upper_bound = sum(sweetness) // (k + 1) | ||
|
|
||
| while lower_bound < upper_bound: | ||
| current_sweetness = 0 | ||
| pieces_count = 0 | ||
| mid = (lower_bound + upper_bound + 1) // 2 | ||
|
|
||
| for s in sweetness: | ||
| current_sweetness += s | ||
| if current_sweetness >= mid: | ||
| pieces_count += 1 | ||
| current_sweetness = 0 | ||
|
|
||
| # check if pieces_count is at least k+1 | ||
| if pieces_count >= k + 1: | ||
| lower_bound = mid | ||
| else: | ||
| upper_bound = mid - 1 | ||
|
|
||
| return lower_bound | ||
|
|
||
|
|
||
| def maximize_sweetness_2(sweetness: List[int], k: int) -> int: | ||
| """ | ||
| Finds the maximum possible sweetness of the piece you will receive if you cut the chocolate bar optimally. | ||
| Args: | ||
| sweetness (List[int]): A list of sweetness values for each chunk of the chocolate bar. | ||
| k (int): The number of friends to share the chocolate bar with. | ||
| Returns: | ||
| int: The maximum possible sweetness of the piece you will receive if you cut the chocolate bar optimally. | ||
| """ | ||
| low, high = 1, sum(sweetness) // (k + 1) | ||
| result = low | ||
| while low <= high: | ||
| mid = (low + high) // 2 | ||
| if can_divide(sweetness, k, mid): | ||
| result = mid | ||
| low = mid + 1 | ||
| else: | ||
| high = mid - 1 | ||
| return result | ||
|
|
||
|
|
||
| def can_divide(sweetness: List[int], k: int, min_sweetness: int) -> bool: | ||
| total_sweetness, pieces = 0, 0 | ||
| for sweet in sweetness: | ||
| total_sweetness += sweet | ||
| if total_sweetness >= min_sweetness: | ||
| pieces += 1 | ||
| total_sweetness = 0 | ||
| return pieces >= k + 1 |
28 changes: 28 additions & 0 deletions
28
algorithms/search/binary_search/divide_chocolate/test_divide_chocolate.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import unittest | ||
| from typing import List | ||
| from parameterized import parameterized | ||
| from algorithms.search.binary_search.divide_chocolate import maximize_sweetness, maximize_sweetness_2 | ||
|
|
||
| TEST_CASES = [ | ||
| ([1, 2, 3, 4, 5, 6, 7, 8, 9], 5, 6), | ||
| ([5], 0, 5), | ||
| ([1, 2, 2, 1, 2, 2, 1], 3, 2), | ||
| ([1, 1, 1, 1, 1, 1, 1], 6, 1), | ||
| ([7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7], 20, 7), | ||
| ] | ||
|
|
||
|
|
||
| class MaximizeSweetnessTestCase(unittest.TestCase): | ||
| @parameterized.expand(TEST_CASES) | ||
| def test_maximize_sweetness(self, sweetness: List[int], k: int, expected: int): | ||
| actual = maximize_sweetness(sweetness, k) | ||
| self.assertEqual(expected, actual) | ||
|
|
||
| @parameterized.expand(TEST_CASES) | ||
| def test_maximize_sweetness_2(self, sweetness: List[int], k: int, expected: int): | ||
| actual = maximize_sweetness_2(sweetness, k) | ||
| self.assertEqual(expected, actual) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| unittest.main() |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.