题目描述
思路解析
一句话答案:LeetCode 1011 在 D 天内送达包裹的能力:按序装船求最小运力,用二分答案+贪心判定。运力越大所需天数越少这层单调让我们二分猜运力、用 days() 验证再收缩,时间 O(n·log(sum))、空间 O(1)。
在 D 天内运完,最小运力到底指什么
一排包裹 weights 按顺序装船。船每天有装载上限 cap(运力),当天总重不超过 cap,装不下留到第二天。给定天数 D,问最小运力多少可在 D 天内运完。题面 weights=[1,2,3,4,5,6,7,8,9,10]、D=5,答案 15:定 15 恰好 5 天,小到 14 就得 6 天。
从最重的包裹一路试到总和,为什么太慢
运力候选有个范围:最小是最重那件包裹(记 max,再小它单独都装不上船),最大是总和 sum(够大一天全运走)。最朴素是从 max 到 sum 逐个运力试、每个都模拟装船看够不够 D 天。可这段能有 sum−max 那么宽,逐个试是 O(sum·n)、n 为包裹数,白扫大量注定不行的运力。
运力和天数为什么是反着走的
逐个试没利用一条规律:运力越大天数越少,两者反向,这叫单调。天数随运力单调,「够不够 D 天」就有分界:某运力起往上全够、往下全不够。于是能用二分答案——先猜一个运力,用判定函数验证够不够,再按结果砍掉一半,砍的正是运力范围。
days(cap) 怎么写,够用了往哪半收
判定函数 days(cap) 算「运力 cap 要几天」:从第 1 天、当天已装 cur=0 起,按序拿每个包裹 w,若 cur+w 超过 cap 就塞不下,天数加一、cur 归零另开一天再装 w;扫完的天数即结果,是能塞就塞的贪心。
拿它和 D 比就知往哪收缩:l=max、r=sum,中点 mid=(l+r)//2。days(mid)≤D 则够用、r=mid 往左试;否则太小、l=mid+1。l 与 r 相遇处即答案。
weights 调几次,15 是怎么逼出来的
区间记 [l,r],初始 [10,55](最重包裹与总和)。第 1 轮 [10,55] mid=32,days=2≤5 够用,r=32。第 2 轮 [10,32] mid=21,days=3≤5 够用,r=21。第 3 轮 [10,21] mid=15,days=5≤5 恰好够用,r=15。第 4 轮 [10,15] mid=12,days=6>5 不够,l=13。第 5 轮 [13,15] mid=14,days=6>5 还不够,l=15。l=r=15,返回 15。
把运力 15 的装船摊开数一遍:第一天按序装 1、2、3、4、5 正好满 15,第 6 件到 21 装不下;第二天装 6、7(合 13),第 8 件又到 21 塞不进;第三天装 8,第四天装 9,第五天装 10。五天正好清空,这就是 days(15)=5 的由来。
l=mid 忘了加一,二分卡在原地出不来
最容易卡死的写法:days(mid)>D 时写成 l=mid 而非 l=mid+1,当 l、r 只差一格、下取整的 mid 正好等于 l,l 就停在原地、区间缩不动,程序死循环;用 l=mid+1 才保证每轮区间真变小。下界也别取错,l 得从 max 起,从 1 或 0 起会去试最重包裹都装不下的运力。
复杂度:二分约 log(sum) 轮、每轮 days() 扫 n 个包裹,共 O(n·log(sum))、空间 O(1)。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
记住这条「够用就试更小、不够就加大」——下面每一帧都在套它。运力下界是最重的包裹 10(否则它单独都装不下),上界是全部之和 55(一天运完)。
这条数组是「运力的搜索轴」——每格是一个候选运力。l 指向最小候选 10、r 指向最大候选 55。注意:数组里装的不是包裹,是「运力可能取的值」。
第 1 轮开始。还没确定的运力范围是 l..r(亮的这段),区间外灰掉的已经被排除掉了。我们要在这段里取中点来试。
取区间中点 mid(下标 6),对应运力 20。先别急着判断——得真去模拟一遍装船,算出运力 20 需要几天。
运力 20 时,按顺序往船上装、装不下就开新的一天,数下来一共 4 天。把这个 4 和目标 D=5 比一比。
4 天没超过 5 天,说明运力 20 够用。但题目要「最小」运力,也许还能更小——把右边界收到 mid(r=mid,保留 mid 这个可行解继续试更小)。
区间缩小了一半。接着对新区间再取中点试,每轮砍掉一半候选,这就是二分的威力。
第 2 轮开始。还没确定的运力范围是 l..r(亮的这段),区间外灰掉的已经被排除掉了。我们要在这段里取中点来试。
取区间中点 mid(下标 3),对应运力 14。先别急着判断——得真去模拟一遍装船,算出运力 14 需要几天。
运力 14 时,按顺序往船上装、装不下就开新的一天,数下来一共 6 天。把这个 6 和目标 D=5 比一比。
6 天超过了 5 天,运力 14 太小了。最小可行运力一定比它大,把左边界推到 mid+1(mid 这格连同左边全排除)。
区间缩小了一半。接着对新区间再取中点试,每轮砍掉一半候选,这就是二分的威力。
第 3 轮开始。还没确定的运力范围是 l..r(亮的这段),区间外灰掉的已经被排除掉了。我们要在这段里取中点来试。
取区间中点 mid(下标 5),对应运力 16。先别急着判断——得真去模拟一遍装船,算出运力 16 需要几天。
运力 16 时,按顺序往船上装、装不下就开新的一天,数下来一共 5 天。把这个 5 和目标 D=5 比一比。
5 天没超过 5 天,说明运力 16 够用。但题目要「最小」运力,也许还能更小——把右边界收到 mid(r=mid,保留 mid 这个可行解继续试更小)。
区间缩小了一半。接着对新区间再取中点试,每轮砍掉一半候选,这就是二分的威力。
第 4 轮开始。还没确定的运力范围是 l..r(亮的这段),区间外灰掉的已经被排除掉了。我们要在这段里取中点来试。
取区间中点 mid(下标 4),对应运力 15。先别急着判断——得真去模拟一遍装船,算出运力 15 需要几天。
运力 15 时,按顺序往船上装、装不下就开新的一天,数下来一共 5 天。把这个 5 和目标 D=5 比一比。
5 天没超过 5 天,说明运力 15 够用。但题目要「最小」运力,也许还能更小——把右边界收到 mid(r=mid,保留 mid 这个可行解继续试更小)。
区间收缩到只剩一格,l 和 r 重合——二分结束,这一格就是答案。
l 和 r 在运力 15 处重合,这就是「5 天内运完的最小运力」。验证:15 要 5 天(不超),14 要 6 天(超了)。绿色高亮的就是最终答案。
边界先想清:D 越大、允许的天数越多,需要的运力越小,下限就是最重的那个包裹。
两个高频追问:能不能二分看「判定是否单调」,区间端点看「物理最小/最大可行值」。
参考代码
def shipWithinDays(weights, D): def days(cap): # 运力 cap 需要几天 d, cur = 1, 0 for w in weights: if cur + w > cap: d += 1; cur = 0 # 装不下,开新的一天 cur += w return d l, r = max(weights), sum(weights) # 运力区间 while l < r: mid = (l + r) // 2 if days(mid) <= D: r = mid # 够用,试更小 else: l = mid + 1 # 不够,加大 return l复杂度
- 时间:O(n·log(sum)),二分运力区间约 log(sum−max) 次,每次 days() 扫一遍 n 个包裹
- 空间:O(1),只用 l/r/mid 几个变量,不开额外数组
易错点
面试追问把动画讲成自己的话
追问为什么这道题能二分?二分的前提是什么?
追问二分的区间为什么是 [max(weights), sum(weights)]?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
使结果不超过阈值的最小除数
LeetCode 1283 · 中等 · 沿着 二分答案套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题