Editorial
Core insight
Boats to Save People · Two Pointers
Core Insight for Boats to Save People
The intuition for the optimal solution relies on a Greedy strategy facilitated by sorting.
To minimize the number of boats, we must maximize the number of boats carrying two people. The heaviest person in the current set must go on a boat. The only decision is whether they travel alone or with a partner.
If the heaviest person can share a boat, they should ideally pair with the person who "costs" the least amount of weight capacity—the lightest person.
- If the heaviest person and the lightest person fit together, this is an optimal pairing. It removes the heaviest person (who is the hardest to pair) and consumes the lightest person (who is the easiest to pair but effectively "free" space here).
- If the heaviest person cannot pair with the lightest person, then the heaviest person cannot pair with anyone (since everyone else is heavier than the lightest person). In this case, the heaviest person must take a boat alone.
Visual Description:
Imagine the input array sorted in ascending order. We place a pointer left at index 0 (lightest) and right at index (heaviest).
- We visualize the sum
people[left] + people[right]. - If the sum is within the limit, we "shrink" the window from both sides (both people leave).
- If the sum exceeds the limit, we only "shrink" from the right side (the heavy person leaves alone; the light person stays to potentially pair with the next heaviest).
- This process continues until the pointers cross.
Deep dive
Full breakdown
Full reasoning, pitfalls, and implementation flow
LeetCode 881: Boats to Save People Solution & Explanation
Problem Overview
TL;DR: Sort the weights and use a two-pointer approach to pair the heaviest person with the lightest person whenever possible to minimize the total number of boats.
The Boats to Save People problem on LeetCode requires us to transport a group of people using the minimum number of boats. We are given an array of weights representing people and a weight limit for each boat. The crucial constraints are that each boat can carry at most two people at once, and the combined weight of these two people must not exceed the specified limit.
This is a classic optimization problem found in interviews, often categorized as a greedy algorithm problem. The goal of the LeetCode 881 solution is to efficiently pair individuals to maximize the utility of each boat.
Brute Force Approach for Boats to Save People
A naive approach to solving this problem would involve checking every possible combination of pairings to find the global minimum. This is essentially a partition problem where we try to group elements into the smallest number of subsets such that each subset has a size of at most 2 and a sum less than or equal to limit.
One might attempt a recursive backtracking solution:
- Select the first remaining person.
- Try placing them alone in a boat.
- Try pairing them with every other remaining person (if valid).
- Recurse for the remaining people.
- Return the minimum boat count found among all branches.
Pseudo-code for Brute Force:
function solve(remaining_people):
if remaining_people is empty: return 0
min_boats = infinity
person = remaining_people[0]
# Option 1: Person goes alone
min_boats = min(min_boats, 1 + solve(remaining_people - {person}))
# Option 2: Person pairs with someone else
for other in remaining_people[1:]:
if person + other <= limit:
min_boats = min(min_boats, 1 + solve(remaining_people - {person, other}))
return min_boatsWhy this fails: The time complexity of this approach approaches in the worst case because of the combinatorial branching. Given the constraint , a factorial solution is computationally impossible and will immediately result in a Time Limit Exceeded (TLE) error. We need a solution closer to or .
Algorithm Strategy: Two Pointer Patterns
- Sort: Sort the
peoplearray in ascending order. This transforms the data into a structure where we can access the lightest and heaviest remaining individuals in time. - Initialize Pointers: Set
leftto 0 andrighttopeople.length - 1. - Initialize Counter: Start a variable
boatsat 0. - Iterate: Loop while
left <= right:- Check if
people[left] + people[right] <= limit. - If true, it means the pair fits. Increment
left(lightest person is processed). - Regardless of the condition, the heaviest person (
people[right]) is always processed (either paired or alone). Decrementright. - Increment
boats.
- Check if
- Return: The final value of
boats.
Execution Flow
Consider people = [3, 2, 2, 1] and limit = 3.
- Sort: The array becomes
[1, 2, 2, 3]. - Init:
left = 0(value 1),right = 3(value 3),boats = 0. - Step 1:
- Compare
people[0] (1) + people[3] (3) = 4. - (Limit). The pair does not fit.
- The heaviest person (3) goes alone.
- Update:
rightmoves to 2.leftstays at 0.boatsbecomes 1.
- Compare
- Step 2:
- Compare
people[0] (1) + people[2] (2) = 3. - (Limit). The pair fits.
- Update:
leftmoves to 1,rightmoves to 1.boatsbecomes 2.
- Compare
- Step 3:
- Now
left(1) ==right(1). This points to the remaining person (value 2). - The loop condition
left <= rightis true. - Technically
people[1] + people[1]is not the check logic for a single person; the logic handles the pair check. Sinceleft == right, we can treat it as the person going alone, or simply ensure the loop handles the last element. - In our logic:
people[left] + people[right]would be doubling the person, which is incorrect. However, usually, the logic implies checking distinct indices or handling the equality case. - Correction for equality: When
left == right, there is only one person left. They take a boat. We incrementboatsand break. - Update:
boatsbecomes 3.
- Now
- Result: Return 3.
Proof of Correctness
We can prove this greedy strategy works via an exchange argument.
Let be the heaviest person and be the lightest. Suppose there is an optimal solution that does not pair with , even though .
- Case 1: is alone in . Since , we can move from their boat in to 's boat. This reduces the capacity used in 's original boat (potentially freeing it up entirely) without increasing the number of boats. The new solution is at least as good as .
- Case 2: is paired with someone else, , in . Since the array is sorted, . If , then certainly . We can swap and . is now with . takes 's old spot. Since , the new weight in 's old spot might increase, but since we are just swapping partners, the number of boats remains unchanged.
Thus, the greedy choice of pairing the heaviest with the lightest never prevents us from reaching an optimal solution.
Pattern Reuse Notes
The Two Pointers - Converging pattern used here is highly versatile. It is typically applied when we need to find pairs in a sorted array that satisfy a specific numerical condition.
- LeetCode 11: Container With Most Water: Uses converging pointers to maximize area. The decision to move pointers is based on height constraints rather than sum.
- LeetCode 15: 3Sum: Reduces the 3-element sum problem to a 2-element sum problem (Target Sum) by fixing one number and using converging pointers for the rest.
- LeetCode 16: 3Sum Closest: Similar to 3Sum, but tracks the minimum difference from a target rather than an exact match.
- LeetCode 18: 4Sum: An extension of 3Sum that uses nested loops combined with the converging pointers pattern.
Feynman coach · Before you peek at the code
Can you explain the core insight in your own words?
If you can explain it before reading code, you’re far more likely to recall it under interview pressure.
Reference Implementation
C++ Solution for LeetCode 881
1#include <vector>
2#include <algorithm>
3
4class Solution {
5public:
6 int numRescueBoats(std::vector<int>& people, int limit) {
7 // Sort the people by weight to enable the two-pointer strategy
8 std::sort(people.begin(), people.end());
9
10 int left = 0;
11 int right = people.size() - 1;
12 int boats = 0;
13
14 while (left <= right) {
15 // Check if the lightest and heaviest person can share a boat
16 if (people[left] + people[right] <= limit) {
17 left++; // Lightest person is included
18 }
19 // Heaviest person is always included (either shared or alone)
20 right--;
21 boats++;
22 }
23
24 return boats;
25 }
26};Java Solution for LeetCode 881
1import java.util.Arrays;
2
3class Solution {
4 public int numRescueBoats(int[] people, int limit) {
5 // Sort the array to use two pointers
6 Arrays.sort(people);
7
8 int left = 0;
9 int right = people.length - 1;
10 int boats = 0;
11
12 while (left <= right) {
13 // If the lightest person fits with the heaviest, pair them
14 if (people[left] + people[right] <= limit) {
15 left++;
16 }
17 // The heaviest person always gets on a boat
18 right--;
19 boats++;
20 }
21
22 return boats;
23 }
24}Python Solution for LeetCode 881
1class Solution:
2 def numRescueBoats(self, people: list[int], limit: int) -> int:
3 # Sort weights to apply greedy two-pointer logic
4 people.sort()
5
6 left = 0
7 right = len(people) - 1
8 boats = 0
9
10 while left <= right:
11 # Check if the lightest person can accompany the heaviest
12 if people[left] + people[right] <= limit:
13 left += 1
14
15 # Heaviest person always takes a seat
16 right -= 1
17 boats += 1
18
19 return boats