题目描述
思路解析
一句话答案:LeetCode 881 救生艇:每艘船最多两人、体重和不超上限,求最少船数。把体重排序,让最轻最重从两端对撞配对,最重者每轮必占一船、能捎最轻就捎,时间 O(n log n)。
最少几艘船能把所有人送走
每个人有一个体重,一艘船最多两人,且两人体重之和不超过上限 limit。给出体重数组 people 和 limit,求最少几艘船送走所有人,题目保证没人重到单人都坐不下。题面例子 people = [3,5,3,4,1,2,2,1],limit = 5,答案 5 艘。
为什么不能挨个组合去试
一艘船最多两人,直觉会想枚举所有两两配对、挑用船最少的那种。可 n 人两两配对的组合数随人数爆炸增长,几十人就多到算不完。这条路把配法平等对待、逐一验算,没用上体重能排序这个信息。
最重的人必占一个船位
先把体重从小到大排好,再盯住最重的人:他这一船的位置省不掉,总得有船带上他。既然船位躲不开,就顺便看能不能塞进第二人,最划算的是当前最轻的那个。连最轻的都和他凑不进上限,更重的更不行,这船只能他独坐;最轻的能拼上就搭这一程,一次送两人。
于是配对从两端往中间收:下标 l 盯最轻、下标 r 盯最重,各自往里走,这就是双指针对撞。每一步都围着当下最重的人定这一船怎么坐、坐定就不再挪动;这种贪心最省,是因为最重者的船位本就躲不掉,我们只是在这艘躲不掉的船上顺手多捎个最轻的。
把这套配对写成代码
先给 people 排序,l 从 0、r 从末尾出发,boats 记船数。每轮看 people[l] 加 people[r]:和不超 limit 就让 l 右挪一格;无论拼没拼上最重的这船都要开,r 每轮左移、boats 加一。循环条件用 l 小于等于 r,让两指针重合时还为最后一人开一艘,直到 l 越过 r 返回 boats。
拿题面这组人过一遍配对
先排序,people = [3,5,3,4,1,2,2,1] 排成 [1,1,2,2,3,3,4,5],l 指最左的 1、r 指最右的 5,boats 从 0 起。第一轮 1 加 5 得 6 超 5 搭不上,l 不动、r 左移到 4,boats 变 1。第二轮 1 加 4 得 5 不超,两指针各挪一步到 3,boats 变 2。第三轮 1 加 3 得 4 能拼,一起走,boats 变 3。
第四轮 2 和 3 加起来 5,又拼一艘,boats 变 4,l 和 r 收拢到中间的 2 上重合。最后一轮两指针都指这个 2,它自己凑成一船,boats 变 5,r 左移后 l 越过 r 停下。一共 5 艘船,正是题面答案。
复杂度和两个易写坏的地方
开头那次排序吃掉了大半时间,要 O(n log n);之后双指针从两端扫到中间一遍是 O(n),相加仍 O(n log n)。除排序外只用 l、r、boats 三个变量、没开额外数组,空间 O(1)。
有两处写着写着就错。排序不能省,最轻配最重只在有序时成立,跳过它结果会偏。r 每轮都得左移,因为最重者这船必开;只有 people[l] 加 people[r] 不超上限 l 才右移,拼不上也动 l 就把该等下一艘的最轻者提前送走了。循环条件写 l 小于等于 r,写成 l 小于 r 会在两指针重合时提前退出,漏掉最后单独坐船那人。
▶ 动画逐步走查(文字版)——想跟着上方动画一帧帧对照就展开
记住这一句:最重的人必上这艘船,最轻的人能拼就拼、拼不上就让他等下一艘。指针 l 从左、r 从右往中间收。
这是大家原本的体重,顺序是乱的。第一步要做的,是把它们从小到大排好。
把所有人按体重从小到大重新站队。排完之后,最轻的在最左边、最重的在最右边。
排好序了。现在左边是最轻的人、右边是最重的人,可以开始配对上船了。
放好两个指针:l 在最左(最轻),r 在最右(最重)。只要 l 还没越过 r,就一直配对开船。
看最轻的 1 和最重的 5:加起来 6,超过了 5,拼不下。最重的人(绿色)这艘船必上。
拼不下,这艘船只坐最重的那个人(绿色),boats 加一变成 1。最轻的人留着下次再拼。
最重的那个人已送走(变暗)。r 向左挪一格去找下一个最重的;l 不动。
看最轻的 1 和最重的 4:加起来 5,不超过 5,这两人能拼一艘。最重的人(绿色)这艘船必上。
这两人一起上这艘船,boats 加一变成 2(绿色的两位)。
这两人已送走(变暗)。l 向右挪一格、r 向左挪一格,继续看新的最轻与最重。
看最轻的 1 和最重的 3:加起来 4,不超过 5,这两人能拼一艘。最重的人(绿色)这艘船必上。
这两人一起上这艘船,boats 加一变成 3(绿色的两位)。
这两人已送走(变暗)。l 向右挪一格、r 向左挪一格,继续看新的最轻与最重。
看最轻的 2 和最重的 3:加起来 5,不超过 5,这两人能拼一艘。最重的人(绿色)这艘船必上。
这两人一起上这艘船,boats 加一变成 4(绿色的两位)。
这两人已送走(变暗)。l 向右挪一格、r 向左挪一格,继续看新的最轻与最重。
只剩最后一个人(l 和 r 重合)。他自己单独坐一艘船。
最后这一个人上船,boats 加一变成 5。所有人都送完了。
这两人已送走(变暗)。l 越过了 r,所有人都安排完了。
l 越过了 r,所有人都安排上了船。一路数下来一共开了 5 艘船,这就是答案。
三个高频追问:贪心为什么最优、等于 limit 的边界、以及 l==r 那一轮的含义。
参考代码
def numRescueBoats(people, limit): people.sort() # 体重从小到大 l, r = 0, len(people) - 1 # 最轻 / 最重 boats = 0 while l <= r: if people[l] + people[r] <= limit: l += 1 # 最轻的也能拼上 r -= 1 # 最重的人必上船 boats += 1 return boats复杂度
- 时间:O(n log n),主要花在排序上;双指针配对只扫一遍是 O(n)
- 空间:O(1),排好序后只用 l、r、boats 三个变量,不开额外数组
易错点
面试追问把动画讲成自己的话
追问为什么“最轻配最重”是最优的,而不是别的配法?
追问如果某个人体重正好等于 limit 会怎样?
追问l==r 时那一轮在做什么?
这道题到这就讲完了。动画和文字是同一套思路——别光看,关掉页面自己默写一遍。然后顺着主线继续:
特殊等价字符串组
LeetCode 893 · 中等 · 沿着 贪心套路 继续往下推进
把这道题真正学会,再走
图解算法年卡 ¥99 /年
- ✓本题每步动画的吴师兄语音讲解(全站陆续覆盖)
- ✓小欧带 8 步通关训练——追问到不看答案也能写对、能 30 秒讲给面试官听
- ✓学习报告,记录每道题的掌握程度
76k+ GitHub Star · 吴师兄开源图解算法,几十万开发者在看的算法讲解
想成体系刷透这类套路?去图解算法专题