Problem overview
You have an IntArray weights with one weight per student and as many boats as you like. A boat carries one or two students. Two students may share a boat only if
- their combined weight is at most
maxWeight, and - the difference between their weights is at most
maxDiff.
The function returns the smallest number of boats that carries everyone.
With weights = [2, 4, 2, 1], maxWeight = 6 and maxDiff = 1 the answer is 3: the students weighing 1 and 2 share a boat, and the other 2 and the 4 each go alone. The 4 can't pair with a 2, since the difference of 2 is larger than maxDiff.
With [81, 37, 32, 88, 55, 93, 45, 72], maxWeight = 100 and maxDiff = 10 the answer is 6, from the pairs (37, 32) and (55, 45) plus four students alone.
Approach
Every boat that carries two students saves one boat compared with sending everyone alone. So the task is to form as many valid pairs as possible: with n students and p pairs the answer is n - p.
A greedy strategy works on the sorted weights. Take the heaviest student first. Heavy students have the fewest possible partners, because the sum limit is the hardest for them to meet. Give each one the heaviest partner that is allowed, since that partner is also the one with the fewest options among the lighter students. The code follows exactly this order.
The difficulty is finding that partner quickly. A partner must satisfy two conditions at once:
- sum:
weights[p] + weights[i] <= maxWeight, which holds for the lighter students up to some point; - difference:
weights[i] - weights[p] <= maxDiff, which holds for the heavier of them.
As i goes down through lighter students, the sum limit only gets looser, so the set of students allowed by the sum only grows. The pointer p moves right once through the sorted array and collects them. Among the collected students the heaviest is the closest in weight to i, and the code keeps them in a stack where the heaviest is on top.
Step by step
- Sort
weightsin ascending order withweights.sort(). - Counters.
answercounts the boats andpis the index of the next light student to consider. Both arevars that start at 0. - Taken students.
isTakenis aHashSetof indices of students who already have a partner. - The stack.
stackis anArrayList<IntArray>of pairs (weight, index). Weights are pushed in ascending order, so the last one pushed is always the heaviest, and a plain stack does the job of a max heap here. - Heaviest first. The loop runs
ifromweights.size - 1down to0usingdownTo. - Collect candidates by sum. While
p < iandweights[p] + weights[i] <= maxWeight, studentpfits withiby weight. Since laterivalues are lighter, this student keeps fitting, so each student is pushed once. - Push. Add
intArrayOf(weights[p], p)to the stack and incrementp. - Skip taken students. If
iis inisTaken, it already shares a boat with a heavier student that was counted, socontinue. - Look for a partner. The next
whileloop runs while the stack is not empty and the top's weight is withinmaxDiffofweights[i]. If even the top, the closest in weight, is too far away, every other candidate is too. - Discard unusable tops. Read the top into
top. Iftop[1] == i(the student is the candidate itself) or the candidate is inisTaken, pop it andcontinue. - Pair them. Otherwise add both
iandtop[1]toisTaken, pop the top andbreak. - Count the boat. After the search,
answer++. It runs once for a student alone and once for a pair, because the partner is skipped later in step 8. - Return
answerafter the loop.
The solution in Kotlin
class Solution {
fun minBoats(weights: IntArray, maxWeight: Int, maxDiff: Int): Int {
weights.sort()
var answer = 0
var p = 0
val isTaken = HashSet<Int>()
val stack = ArrayList<IntArray>()
for (i in weights.size - 1 downTo 0) {
while (p < i && weights[p] + weights[i] <= maxWeight) {
stack.add(intArrayOf(weights[p], p))
p++
}
if (isTaken.contains(i)) continue
while (stack.isNotEmpty() && weights[i] - stack[stack.size - 1][0] <= maxDiff) {
val top = stack[stack.size - 1]
if (top[1] == i || isTaken.contains(top[1])) {
stack.removeAt(stack.size - 1)
continue
}
isTaken.add(i)
isTaken.add(top[1])
stack.removeAt(stack.size - 1)
break
}
answer++
}
return answer
}
}
Kotlin details worth knowing:
weights.sort()sorts theIntArrayin place and returnsUnit. The caller's array changes.sorted()would return a newListand leave the array alone.for (i in weights.size - 1 downTo 0)counts down and includes both ends. A normal range such as0..nwould be empty if it were written backwards.stack[stack.size - 1]reads the last element of theArrayListandremoveAt(stack.size - 1)removes it. Each element is anIntArrayof two numbers,[0]for the weight and[1]for the index.- The
continueinside the innerwhilerepeats thatwhileloop, not the outerfor. ThecontinueafterisTaken.contains(i)is outside the inner loop and goes to the nexti. - Kotlin has no
int/Integersplit in the source:HashSet<Int>holds boxed values, butcontainscompares by value.
Complexity
Time: O(n log n). Sorting costs O(n log n). After that, the pointer p moves at most n times and every student is pushed once and popped at most once, so the main loop is O(n) with constant-time set and list operations on average.
Space: O(n). The stack and the set can each hold up to n entries. sort() works in place on the array.
Edge cases and common mistakes
- Nobody can pair. If
maxDiffis small or weights are close tomaxWeight, each student takes a boat and the answer isweights.size. - Only the sum limit. With a large
maxDiffthe difference check never stops the search, and the problem becomes the classic two-pointer boats problem. - Empty array. The loop never runs and the result is 0.
- Counting both students of a pair. The pair is one boat. That's the reason a partner is recorded in
isTakenand skipped when its turn comes. - Forgetting the
top[1] == icheck. A student can be on the stack and still be the one currently being placed, and must not be paired with itself. - Leaving
weightsunsorted. Both the pointerpand the "top is the heaviest" idea depend on the ascending order.
Testing
[2, 4, 2, 1]withmaxWeight = 6,maxDiff = 1returns 3.[81, 37, 32, 88, 55, 93, 45, 72]withmaxWeight = 100,maxDiff = 10returns 6.- An empty array returns 0.