Last Stone Weight (LC 1046)
On this page
Pattern: Max-Heap / Greedy with Heap
Difficulty: Easy
Key Concept: Repeatedly take the two heaviest stones using a max-heap; smash and push back the remainder until at most one stone remains.
Problem Statement
You have an array stones where stones[i] is the weight of the i-th stone.
Each turn:
- Choose the two heaviest stones (weights
xandywithx <= y). - Smash them together:
- If
x == y, both are destroyed. - If
x != y, the stone of weightyis destroyed, and the stone of weightxbecomesy - x.
- If
Repeat until there is at most one stone left.
Return the weight of the last remaining stone, or 0 if none remain.
Input
int[] stones— positive weights.
Output
int— last stone’s weight, or0.
Constraints (typical)
1 <= stones.length <= 301 <= stones[i] <= 1000
1. Algorithm & Pseudocode
Brute force (sort every turn)
while more than one stone:
sort stones descending
y = stones[0] // heaviest
x = stones[1] // second heaviest
remove both from structure
if y > x:
insert (y - x) back
if one stone left: return it
else: return 0
Optimal (max-heap)
pq = max-heap of all stone weights
while pq.size() > 1:
y = pq.poll() // largest
x = pq.poll() // second largest
if y > x:
pq.offer(y - x)
if pq is empty: return 0
else: return pq.poll()
Why this works
Each turn only the relative order of the two largest matters; a max-heap gives O(log n) access to the top two weights without re-sorting the whole array.
2. Step-by-Step Analysis (Beginner-Friendly)
-
Why always the two heaviest?
The problem statement fixes the rule: every turn uses the maximum and second maximum remaining. Any solution must simulate that exactly. -
Why not sort once and merge?
After a smash, you insert a new weight (y - x). The global order changes; you need a dynamic structure that can repeatedly extract maxima. -
Max-heap vs min-heap
Java’sPriorityQueueis a min-heap. For “largest first,” useCollections.reverseOrder()or a comparator that orders descending. Conceptually you want “poll = heaviest.” -
Comparator safety:
b - avsInteger.compare(b, a)
Subtracting can overflow for largeintvalues (not in LeetCode’s tiny constraints here, but good habit).Integer.compare(b, a)compares without arithmetic overflow. -
When do we push
y - x?
Only wheny > x. Ify == x, both disappear—nothing to push. Ify < x, that cannot happen ifyis the max andxthe second max (soy >= xalways). -
Loop until size ≤ 1
With one stone, that is the answer. With zero stones, return0.
3. The Dry Run
Input: stones = [2, 7, 4, 1, 8, 1].
We trace the multiset of weights after each turn. The max-heap implements “always take two largest”; poll order is largest first, then second largest.
| Turn | Stone count | Weights sorted (desc) | First poll y |
Second poll x |
Smash | New weight pushed? | Weights after (desc) |
|---|---|---|---|---|---|---|---|
| — | 6 | 8, 7, 4, 2, 1, 1 | — | — | — | — | (initial) |
| 1 | 6 → 5 | 8, 7, 4, 2, 1, 1 | 8 | 7 | 8 - 7 = 1 |
yes: 1 | 4, 2, 1, 1, 1 |
| 2 | 5 → 4 | 4, 2, 1, 1, 1 | 4 | 2 | 4 - 2 = 2 |
yes: 2 | 2, 1, 1, 1 |
| 3 | 4 → 3 | 2, 1, 1, 1 | 2 | 1 | 2 - 1 = 1 |
yes: 1 | 1, 1, 1 |
| 4 | 3 → 1 | 1, 1, 1 | 1 | 1 | equal → both gone | no | 1 |
| 5 | 1 | 1 | — | — | stop (size ≤ 1) |
— | 1 |
Final answer: 1.
Heap operations only (compact trace)
| Turn | poll (1st) |
poll (2nd) |
offer |
|---|---|---|---|
| 1 | 8 | 7 | 1 |
| 2 | 4 | 2 | 2 |
| 3 | 2 | 1 | 1 |
| 4 | 1 | 1 | — |
| done | — | — | return 1 |
4. Java Solution
Brute Force
Idea: Use a list, sort descending each turn, remove two largest, insert difference if needed.
Time: O(n^2 log n) in the worst case (up to O(n) turns, each sort O(n log n)). Space: O(n).
import java.util.*;
class SolutionBruteForce {
public int lastStoneWeight(int[] stones) {
List<Integer> list = new ArrayList<>();
for (int w : stones) {
list.add(w);
}
while (list.size() > 1) {
list.sort(Collections.reverseOrder());
int y = list.remove(0);
int x = list.remove(0);
if (y > x) {
list.add(y - x);
}
}
return list.isEmpty() ? 0 : list.get(0);
}
}
Optimal
Time: O(n log n) for heap build plus O(n) iterations with O(log n) heap work → O(n log n) overall. Space: O(n).
import java.util.Collections;
import java.util.PriorityQueue;
class Solution {
public int lastStoneWeight(int[] stones) {
PriorityQueue<Integer> pq = new PriorityQueue<>(Collections.reverseOrder());
for (int w : stones) {
pq.offer(w);
}
while (pq.size() > 1) {
int y = pq.poll();
int x = pq.poll();
if (y > x) {
pq.offer(y - x);
}
}
return pq.isEmpty() ? 0 : pq.peek();
}
}
Safer comparator (overflow-aware):
PriorityQueue<Integer> pq = new PriorityQueue<>((a, b) -> Integer.compare(b, a));
5. The “Java vs. Others” Edge
- Java:
PriorityQueue+Collections.reverseOrder()gives a max-heap without negating integers (Python’sheapqis min-only, so you often negate weights). - C++:
std::priority_queue<int>is already a max-heap—same idea, opposite default vs Java. - Avoid
(a, b) -> b - afor generalintweights: subtraction can overflow; preferInteger.compare(b, a). poll()vspeek(): After the loop, usepeek()orpoll()once; checkisEmpty()before returning0.
6. Complexity Summary
| Approach | Time | Space | Notes |
|---|---|---|---|
| Brute Force | O(n^2 log n) worst case |
O(n) |
Re-sort each turn; educational only. |
| Optimal | O(n log n) |
O(n) |
Max-heap; each smash does heap ops in O(log n). |