Problem
You have a collection of stones with positive integer weights. Each round, pick the two heaviest stones and smash them together: if they weigh the same, both are destroyed; if they differ, the lighter one is destroyed and the heavier one survives with its weight reduced by the lighter stone's weight. Repeat until at most one stone remains and return its weight, or 0 if nothing is left.
- Input: stones = [2, 7, 4, 1, 8, 1]
- Output: 1
- Explanation: smash 8 and 7 โ 1 remains; smash 4 and 2 โ 2 remains; smash 2 and 1 โ 1 remains; smash 1 and 1 โ both destroyed. One stone of weight 1 survives.
Intuition
Every round demands the two current maximum values โ an inherently dynamic query because the heap changes after each smash. A max-heap delivers each maximum in O(log n) and re-inserts the remainder stone in O(log n), so each round costs O(log n) instead of an O(n) linear scan through the whole collection.
Solution โ Max-Heap
Push all stones onto a max-heap, then repeatedly pop the two heaviest and push back their difference if they are unequal. Stop when one or zero stones remain.
- Build a max-heap from all stone weights.
- While more than one stone is in the heap:
a. Pop the heaviest stone.
b. Pop the second-heaviest stone.
c. If they differ, push
heaviest โ second_heaviestback onto the heap. - Return the remaining stone's weight, or 0 if the heap is empty.
1import heapq
2
3def lastStoneWeight(stones: list[int]) -> int:
4 max_heap = [-stone for stone in stones] # negate to simulate max-heap with Python's min-heap
5 heapq.heapify(max_heap)
6
7 while len(max_heap) > 1:
8 heaviest = -heapq.heappop(max_heap)
9 second_heaviest = -heapq.heappop(max_heap)
10 if heaviest != second_heaviest:
11 heapq.heappush(max_heap, -(heaviest - second_heaviest)) # remainder stone survives
12
13 return -max_heap[0] if max_heap else 0Time: O(n log n) โ heapify is O(n) and each of at most n rounds performs two pops and one push, each O(log n).
Space: O(n) โ the heap stores all stones simultaneously.
Complexity Summary
| Approach | Time | Space | When to use |
|---|---|---|---|
| Max-Heap | O(n log n) | O(n) | Any time you need repeated access to the current maximum of a changing collection |
Common Mistakes
- Forgetting to negate values in Python โ
heapqis a min-heap, so storingstoneinstead of-stonecauses the lightest stones to be smashed each round, producing completely wrong results. - Not guarding against an empty heap after the loop โ when every smash produces equal-weight pairs, all stones cancel out and the heap ends up empty; reading
heap[0]on an empty structure crashes. - Pushing the remainder even when both stones are equal โ equal stones destroy each other entirely and produce nothing; pushing a weight of 0 corrupts the heap and can cause the loop to run one extra iteration.
- Re-sorting the array each round instead of using a heap โ sorting after every smash costs O(n log n) per round and O(nยฒ log n) total; a heap reduces per-round work to O(log n).
- Checking
len > 0as the loop condition โ the loop needs at least two elements to smash; exiting atlen == 1is correct, butlen > 0would try to pop a second stone from a single-element heap.
Related Problems
Kth Largest Element in an Arrayโ uses a heap to efficiently surface the kth largest without a full sortTop K Frequent Elementsโ heap-based selection of the k most frequent elements from a dynamic countK Closest Points to Originโ selects k smallest elements using a heap keyed on distanceMerge K Sorted Listsโ min-heap merges multiple sorted sequences by always popping the current global minimumFind K Closest Elementsโ heap-based selection ordered by absolute difference from a target