题目描述
思路解析动画文字版
想象一个开关:天数小的时候做不出 m 束(false),天数大到某个点开始一直能做(true)。一条 false…falsetrue…true 的线——我们要找的就是第一个 true 的位置。单调 = 可二分。
二分范围 = 最小开放日到最大开放日(再早没花开、再晚没意义)。判定函数 canMake(d):从左到右扫,遇到已开的花就接到当前连续段上,每攒够 k 朵立刻摘成一束、计数清零;遇到没开的花连续段就断掉。够 m 束就 true。
判定 canMake · 试 d=2:拿小天数 d=2 试水。开放日 ≤ 2 的花算开了:下标 0(日1)、2? 不,下标 2 是日 3 > 2 还没开。下标 1、3(日10)和下标 2(日3)都没开,只有下标 0、4 开了。下面一朵一朵扫、攒连续段。
d=2 扫描 · 第0朵 开:指针到第 0 朵,日 1 ≤ 2 已开,连续段长 run=1。k=1,攒够了!立刻摘成 1 束,run 清零重新攒。已有 1 束。
d=2 扫描 · 第1朵 没开:指针到第 1 朵,日 10 > 2 没开。一旦碰到没开的花,当前连续段就断了,run 归零——花束必须是相邻的 k 朵,中间隔着没开的花不能拼。
d=2 扫描 · 第2朵 没开:指针到第 2 朵,日 3 > 2 也没开,run 保持 0。这朵花要等到第 3 天才开,现在还轮不到它。
d=2 扫描 · 第3朵 没开:指针到第 3 朵,日 10 还是没开,run 仍是 0。前面 4 朵只摘出了 1 束。
d=2 扫描 · 第4朵 开:指针到最后一朵,日 2 ≤ 2 已开,run=1 攒够 k,再摘 1 束。扫完了,一共 2 束。
d=2 判定 · 2 < 3 不够:d=2 只做出 2 束 < 3,不够。说明真正的答案在 2 的右边——这就是二分要往右收的依据。下面正式开二分。
二分开局 · 范围 [min,max]:现在把舞台当成天数刻度尺:最早第 1 天有花开、最晚第 10 天全开,答案就夹在 [1,10] 之间。lo、hi 两根指针往中间夹,找第一个能做出 m 束的天数。
第1轮 · mid 天数:取中间天数 mid=5。问:等到第 5 天,能不能做出 3 束?用刚才那套贪心扫一遍。
d=5 判定 · 谁开了:第 5 天:开放日 ≤ 5 的花开了——下标 0、2、4(日 1、3、2)。k=1 每朵单独成束,三朵分别成段,3 束 ≥ 3,做得出!
第1轮 · 够 → hi=mid:d=5 够了!那答案一定 ≤ 5,hi 收到 5。注意是 hi=mid 不是 mid−1——因为第 5 天本身可能就是最优解,得留着。范围缩成天数 [1,5]。
第2轮 · mid 天数:在新范围 [1,5] 里取中间天数 mid=3。再问:第 3 天够不够 3 束?
d=3 判定 · 谁开了:第 3 天:下标 2 的花(日 3)正好开了,加上下标 0、4,还是 3 朵开。k=1 → 3 束 ≥ 3,依然够!
第2轮 · 够 → hi=mid:第 3 天也够,hi 收到 3。答案继续往左压,范围只剩 [1,3]。我们还在找「第一个够的天数」,所以哪怕够了也要试更小的。
第3轮 · mid 天数:范围 [1,3] 取中点 mid=2。第 2 天够不够?这个我们开头已经扫过——只做出 2 束。
第3轮 · 不够 → lo=mid+1:第 2 天只做出 2 束,不够。说明答案严格大于 2,lo 推到 3。现在 lo 和 hi 都指向天数 3,要撞上了。
二分结束 · lo=hi=3:lo 撞上 hi,二分停下。答案就是 3 天——比 3 小做不出 3 束,第 3 天恰好够,正是那条单调线上第一个 true 的位置。
看懂连续段 · 换个 k=3 例子:k=1 时每朵单独成束,看不出「连续」的关键。换个例子 bloomDay=[7,7,7,7,12,7,7],k=3:现在每束要挨着的 3 朵。下标 4 的花要等到第 12 天,它把数组劈成了两段。
k=3 · 试 d=7 数连续段:第 7 天,下标 4(日12)还没开把队伍劈成两段。左段 4 朵连着,每 3 朵一束 → 摘 1 束(剩 1 朵不够);右段只 2 朵 → 0 束。共 1 束。要 m=2 束就不够。
k=3 · 试 d=12 全开:等到第 12 天,下标 4 的花也开了,7 朵连成一整段,每 3 朵一束 → 摘 2 束(剩 1 朵),够 m=2!这例子答案 12。看清了吗——缺口决定能凑几束,这就是为什么要数「连续段」。
这是「二分答案」的通用套路:不在数组下标上二分,而是在答案本身的取值范围上二分,配一个 O(n) 的 check 函数判可行性。LC410 分割数组、LC875 吃香蕉、LC1011 运货都是同一招——认出单调性是关键。
边界三连:三种极端:花不够(−1)、刚好全用(等最晚那朵)、k=1(退化成找第 m 小)。判定函数和二分范围天然兜住它们。
面试追问:把「为何能二分」「判定怎么贪心」「死局与溢出」讲透,是二分答案类题的面试满分模板。
参考代码
class Solution: def minDays(self, bloomDay, m, k): if m * k > len(bloomDay): return -1 # 花不够,永远做不出 def canMake(day): # d 天能做出 ≥ m 束? bouquets = run = 0 for b in bloomDay: if b <= day: # 这朵开了,接上连续段 run += 1 if run == k: # 攒够 k 朵,摘一束 bouquets += 1; run = 0 else: # 没开,连续段断掉 run = 0 return bouquets >= m lo, hi = min(bloomDay), max(bloomDay) while lo < hi: # 二分第一个 true 的天数 mid = (lo + hi) // 2 if canMake(mid): hi = mid # 够 → 答案 ≤ mid else: lo = mid + 1 # 不够 → 答案 > mid return lo复杂度
- 时间复杂度:O(n · log(maxDay)),二分天数 O(log(maxDay−minDay)),每次判定扫整个数组 O(n)
- 空间复杂度:O(1),只用 lo/hi/mid 和 run/bouquets 几个变量
易错点
面试追问把动画讲成自己的话
追问为什么能二分天数?
追问判定函数 canMake 怎么写?
追问复杂度与边界?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
分割数组的最大值
LeetCode 410 · 困难 · 沿着 二分答案套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题