救生艇 图解题解
这道题到底在问什么
- 输入
- people = [3,5,3,4,1,2,2,1], limit = 5
- 输出
- 5
最优解:为什么这么做
一句话答案: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 会在两指针重合时提前退出,漏掉最后单独坐船那人。
▶ 动画逐步走查(共 21 步)——想跟着动画一帧帧对照就展开
- 3记住这一句:最重的人必上这艘船,最轻的人能拼就拼、拼不上就让他等下一艘。指针 l 从左、r 从右往中间收。
- 4这是大家原本的体重,顺序是乱的。第一步要做的,是把它们从小到大排好。
- 5把所有人按体重从小到大重新站队。排完之后,最轻的在最左边、最重的在最右边。
- 6排好序了。现在左边是最轻的人、右边是最重的人,可以开始配对上船了。
- 7放好两个指针:l 在最左(最轻),r 在最右(最重)。只要 l 还没越过 r,就一直配对开船。
- 8看最轻的 1 和最重的 5:加起来 6,超过了 5,拼不下。最重的人(绿色)这艘船必上。
- 9拼不下,这艘船只坐最重的那个人(绿色),boats 加一变成 1。最轻的人留着下次再拼。
- 10最重的那个人已送走(变暗)。r 向左挪一格去找下一个最重的;l 不动。
- 11看最轻的 1 和最重的 4:加起来 5,不超过 5,这两人能拼一艘。最重的人(绿色)这艘船必上。
- 12这两人一起上这艘船,boats 加一变成 2(绿色的两位)。
- 13这两人已送走(变暗)。l 向右挪一格、r 向左挪一格,继续看新的最轻与最重。
- 14看最轻的 1 和最重的 3:加起来 4,不超过 5,这两人能拼一艘。最重的人(绿色)这艘船必上。
- 15这两人一起上这艘船,boats 加一变成 3(绿色的两位)。
- 16这两人已送走(变暗)。l 向右挪一格、r 向左挪一格,继续看新的最轻与最重。
- 17看最轻的 2 和最重的 3:加起来 5,不超过 5,这两人能拼一艘。最重的人(绿色)这艘船必上。
- 18这两人一起上这艘船,boats 加一变成 4(绿色的两位)。
- 19这两人已送走(变暗)。l 向右挪一格、r 向左挪一格,继续看新的最轻与最重。
- 20只剩最后一个人(l 和 r 重合)。他自己单独坐一艘船。
- 21最后这一个人上船,boats 加一变成 5。所有人都送完了。
- 22这两人已送走(变暗)。l 越过了 r,所有人都安排完了。
- 23l 越过了 r,所有人都安排上了船。一路数下来一共开了 5 艘船,这就是答案。
⚠️ 容易写错的地方
✗ 错:忘了先排序就上来配对
✓ 对:先 sort,再用 l/r 从两端往中间收
贪心“最轻配最重”成立的前提就是有序,不排序结果会错
✗ 错:无论能不能拼,都同时移动 l 和 r
✓ 对:r 每轮必减;只有拼得上时 l 才加一
拼不上时这艘船只坐 r 一人,最轻的人要留给下一艘,l 不能动
✗ 错:循环写成 while l < r,漏掉最后单人
✓ 对:写 while l <= r
当 l==r 时还剩一个人,要单独坐一艘船,写成 < 会漏掉这艘
完整代码(Python / C++ / Java)
Python
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 boatsC++
int numRescueBoats(vector<int>& people, int limit){
sort(people.begin(), people.end());
int l = 0, r = people.size() - 1, boats = 0;
while (l <= r) {
if (people[l] + people[r] <= limit) l++;
r--;
boats++;
}
return boats;
}Java
public int numRescueBoats(int[] people, int limit) {
Arrays.sort(people);
int l = 0, r = people.length - 1, boats = 0;
while (l <= r) {
if (people[l] + people[r] <= limit) l++;
r--;
boats++;
}
return boats;
}复杂度
时间
O(n log n)
主要花在排序上;双指针配对只扫一遍是 O(n)
空间
O(1)
排好序后只用 l、r、boats 三个变量,不开额外数组
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 救生艇 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么「最轻配最重」是最优的,而不是别的配法?+
最重的人必占一个船位。在他这一船剩下的位置上,能搭的最好人选就是当前最轻的:如果连最轻的都和他超了上限,那更重的也一样超,这船只能坐他一人;如果最轻的能搭上,就等于白赚一个空位。所以每次让最轻配最重,不会比任何别的配法用更多船。
如果某个人体重正好等于 limit 会怎样?+
他再加上任何一个人都会超过 limit,所以和谁都拼不上,只能自己单独坐一艘。题目保证每个人的体重不超过 limit,这种人一定坐得下,只是占满整艘船而已。
l 和 r 指到同一个人那一轮在干什么?+
这一轮只剩一个人,people[l] 加 people[r] 其实是他自己加自己。代码里 r 减一之后 l 就超过了 r,循环随即结束,而 boats 已经在这一轮为他加了一,等于给他单独开了一艘。所以循环条件必须是 l 小于等于 r,写成 l 小于 r 会把这最后一人漏掉。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 救生艇 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
把这道题真正拿下
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。