1231. 分享巧克力
大约 4 分钟
---
1231. 分享巧克力
- 标签:数组、二分查找
- 难度:困难
题目链接
题目大意
描述:你有一大块巧克力,由一些甜度不完全相同的小块组成,用数组 表示每一小块的甜度。你打算和 名朋友一起分享,所以你需要将巧克力切割 次得到 块,每一块都由一些连续的小块组成。你将会吃掉总甜度最小的一块,其余分给朋友们。
要求:找出一个最佳的切割策略,使得你所分得的巧克力总甜度最大,并返回这个最大总甜度。
说明:
- 。
- 。
示例:
- 示例 1:
输入:sweetness = [1,2,3,4,5,6,7,8,9], K = 5
输出:6
解释:你可以把巧克力分成 [1,2,3], [4,5], [6], [7], [8], [9]。- 示例 2:
输入:sweetness = [5,6,7,8,9,1,2,3,4], K = 8
输出:1
解释:只有一种办法可以把巧克力分成 9 块。解题思路
思路 1:二分查找答案
1. 核心思想
遇到「最大化最小值」或「最小化最大值」的问题,通常可以用二分答案来解决。
我们不是直接找切割方案,而是猜测一个答案 ,然后判断:能否切出至少 块,使得每块的甜度都 ?
如果能,说明 可行,我们尝试更大的值(往右半边找)。如果不能,说明 太大了,需要减小(往左半边找)。
这种「猜答案 + 验证」的思路之所以高效,是因为验证函数(判断能否切出至少多少块)通常可以用贪心 实现,比直接求最优解容易得多。
2. 具体步骤
第 1 步:确定二分范围
- 下界 :每块的最小甜度至少为 (因为 )。
- 上界 :最理想情况是所有块甜度均分,即 。不可能超过这个值,因为总甜度固定。
第 2 步:定义检查函数
贪心地从左到右切分:
- 维护当前块的累加甜度 和已切出的块数 。
- 遍历每个小块,累加到 。
- 当 时,说明当前块已经达到目标甜度,切出一块(),并重置 。
- 遍历结束后,如果 ,返回
True,否则返回False。
第 3 步:二分查找
使用左闭右开或左闭右闭的二分模板。这里使用「找最大值」模板:
- 当 为
True时,(尝试更大的值)。 - 否则 。
- 注意 取 (向上取整),防止死循环。
结合示例 1 走一遍:
,需要 块。
总甜度 ,,。
检查 :
- 遍历:, 切一块;, 切一块; 切一块; 切一块; 切一块; 切一块。共 块 → 可行,。
检查 :
- 遍历: 切一块; 切一块; 切一块; 切一块; 切一块; 切一块。共 块 → 可行,。
检查 :
- 遍历: 切一块; 切一块; 切一块; 切一块; 切一块。共 块 → 不可行,。
,返回 。
3. 为什么贪心检查是正确的
从左到右切分时,只要累加甜度达到 就立刻切一刀,这种策略能最大化切出的块数。因为推迟切分只会让当前块更大,而不会帮助后面的块更容易达到 。所以贪心检查得到的是「最多能切出多少块 的块」,如果这个最大值都达不到 ,那任何其他切法也达不到。
思路 1:代码
class Solution:
def maximizeSweetness(self, sweetness: List[int], K: int) -> int:
# 检查能否切出至少 K+1 块,每块甜度 >= mid
def can_divide(mid):
cur = 0 # 当前块的累加甜度
cnt = 0 # 已切出的块数
for s in sweetness:
cur += s
if cur >= mid:
cnt += 1
cur = 0
return cnt >= K + 1
# 二分范围的下界和上界
left = 1
right = sum(sweetness) // (K + 1)
# 二分查找最大值
while left < right:
mid = (left + right + 1) // 2
if can_divide(mid):
left = mid
else:
right = mid - 1
return left思路 1:复杂度分析
- 时间复杂度:,其中 是数组 的长度, 是甜度总和。二分次数为 ,每次检查需要 遍历数组。
- 空间复杂度:,只使用了常数个变量,没有额外空间开销。