Last Stone Weight
EasyPrompt
You are given an array of integers stones where stones[i] is the weight of the ith stone.
We are playing a game with the stones. On each turn, we choose the heaviest two stones and smash them together. Suppose the heaviest two stones have weights x and y with x <= y. The result of this smash is:
- If
x == y, both stones are destroyed, and - If
x != y, the stone of weightxis destroyed, and the stone of weightyhas new weighty - x.
At the end of the game, there is at most one stone left.
Return the weight of the last remaining stone. If there are no stones left, return 0.
Example 1:
Input: stones = [2,7,4,1,8,1]
Output: 1
Explanation:
We combine 7 and 8 to get 1 so the array converts to [2,4,1,1,1] then,
we combine 2 and 4 to get 2 so the array converts to [2,1,1,1] then,
we combine 2 and 1 to get 1 so the array converts to [1,1,1] then,
we combine 1 and 1 to get 0 so the array converts to [1] then that's the value of the last stone.Example 2:
Input: stones = [1]
Output: 1
Constraints:
1 <= stones.length <= 301 <= stones[i] <= 1000
Approaches
3 approaches with complexity analysis and trade-offs.
This approach directly simulates the stone-smashing process described in the problem. In each step, we need to find the two heaviest stones. A straightforward, albeit inefficient, way to do this is to sort the collection of stones in every iteration and pick the last two elements.
Algorithm
- Convert the input array
stonesinto aList. - Loop while the list size is greater than 1.
- In each iteration, sort the list in ascending order.
- Remove the last two elements (the heaviest stones),
yandx. - If
y > x, add the differencey - xback to the list. - After the loop, if the list is empty, return 0. Otherwise, return the single remaining element.
Walkthrough
We maintain a list of the current stone weights. The simulation proceeds in a loop that continues as long as there is more than one stone.
Inside the loop:
- Sort the list of stones in ascending order. This brings the heaviest stones to the end of the list.
- Identify the two heaviest stones,
y(the last element) andx(the second-to-last element). - Remove both
xandyfrom the list. - If their weights are different (
x != y), calculate the new weighty - xand add it back to the list. If they are the same, they are both destroyed, and nothing is added back.
The loop terminates when one or zero stones remain. If the list is empty, the result is 0. Otherwise, the result is the weight of the single remaining stone.
import java.util.ArrayList;import java.util.Collections;import java.util.List; class Solution { public int lastStoneWeight(int[] stones) { List<Integer> stoneList = new ArrayList<>(); for (int stone : stones) { stoneList.add(stone); } while (stoneList.size() > 1) { Collections.sort(stoneList); int y = stoneList.remove(stoneList.size() - 1); int x = stoneList.remove(stoneList.size() - 1); if (y > x) { stoneList.add(y - x); } } return stoneList.isEmpty() ? 0 : stoneList.get(0); }}Complexity
Time
O(n^2 log n). Let `n` be the initial number of stones. The loop runs up to `n-1` times. In each iteration `i`, we sort a list of size `n-i`, which takes `O((n-i) log (n-i))`. The total time is the sum of these sorting times, which is dominated by the earlier, larger sorts, leading to a complexity of `O(n^2 log n)`.
Space
O(n) to store the stones in a list, where n is the initial number of stones.
Trade-offs
Pros
Simple to understand and implement.
Directly follows the logic from the problem description.
Cons
Highly inefficient due to repeated sorting of the entire list in each step.
Would be too slow for a larger number of stones (
n).
Since the weights of the stones are limited to a relatively small range (1 to 1000), we can use an array as a frequency map (also known as bucket sort or counting sort). This allows us to keep track of the counts of stones of each weight and simulate the process without using a comparison-based sort or a heap data structure.
Algorithm
- Create a
bucketsarray of size 1001 (since max weight is 1000) to store the frequency of each stone weight. - Populate the
bucketsarray by iterating through the inputstones. - Initialize a variable
heaviest = 0to hold the first of two stones to be smashed, and a pointerw = 1000. - Loop
wfrom 1000 down to 1:- If
buckets[w]is 0, continue to the next smaller weight. - If
heaviestis 0, it means we're looking for the first stone. Any pairs of stones of weightwwill smash each other. Ifbuckets[w]is odd, one stone is left over; we setheaviest = wand move to the next smaller weight (w--). - If
heaviestis not 0, we have found our second stone (w). We smash them: decrementbuckets[w], calculatenewWeight = heaviest - w, incrementbuckets[newWeight], and resetheaviest = 0. We do not decrementwyet, as there might be more stones of this weight to process.
- If
- After the loop,
heaviestwill hold the weight of the last stone, or 0.
Walkthrough
We create an array, say buckets, of size 1001, where buckets[w] stores the number of stones with weight w. After populating this array, we simulate the smashing process by iterating downwards from the maximum possible weight (1000). We use a pointer w for the current weight and a variable heaviest to keep track of the first heavy stone found in a smash pair.
The simulation iterates from w = 1000 down to 1. When we find a weight w with a non-zero count, we check if we already have a heaviest stone from a previous, larger weight. If not, we handle all pairs of stones of weight w (which smash to nothing) and if one is left over, it becomes the new heaviest. If we do have a heaviest stone, we smash it with the current stone of weight w, calculate the remainder, and update its count in the buckets array. This new stone will be processed when the loop pointer w reaches its weight.
class Solution { public int lastStoneWeight(int[] stones) { int[] buckets = new int[1001]; for (int stone : stones) { buckets[stone]++; } int heaviest = 0; // To store the weight of the first stone in a smash int w = 1000; // Current weight we are looking at while (w > 0) { if (buckets[w] == 0) { w--; continue; } if (heaviest == 0) { // This is the first stone of a potential smash. // All pairs of weight 'w' smash to 0. if (buckets[w] % 2 == 1) { // If there's an odd number, one 'w' is left over. heaviest = w; } // Move to the next smaller weight. w--; } else { // We have a 'heaviest' stone and now found a stone of weight 'w'. // Smash them. buckets[w]--; // Use one stone of weight 'w'. int newWeight = heaviest - w; buckets[newWeight]++; heaviest = 0; // Reset heaviest as it has been used. // We stay at 'w' to process remaining stones of this weight. } } return heaviest; }}Complexity
Time
O(n + W), where `n` is the number of stones and `W` is the maximum possible weight. It takes `O(n)` to build the buckets and `O(W)` for the main simulation loop.
Space
O(W), where `W` is the maximum possible weight of a stone (1000). This is for the `buckets` array.
Trade-offs
Pros
Efficient when the range of weights
Wis small.Avoids comparison-based sorting and the
log nfactor associated with heaps.
Cons
The logic can be more complex to implement correctly compared to the heap-based solution.
Its performance is dependent on the maximum weight
W. IfWwere very large, this approach would be inefficient.For the given constraints (
n <= 30,W <= 1000), this is slightly slower than the heap approach.
The problem requires repeatedly finding and removing the two largest elements from a collection. A max-heap is the ideal data structure for this task. It provides O(log n) time complexity for insertions and for extracting the maximum element, making it highly efficient for this problem.
Algorithm
- Create a max-heap. In Java, this is a
PriorityQueuewith a reverse order comparator. - Add all elements from the
stonesarray to the heap. - Loop while the heap's size is greater than 1.
- In each iteration,
poll()the two largest elements,yandx. - If
y > x,addthe differencey - xback to the heap. - After the loop, if the heap is not empty, return the top element using
peek(). Otherwise, return 0.
Walkthrough
A max-heap is a specialized tree-based data structure that satisfies the heap property: the value of each node is greater than or equal to the value of its children. This ensures the root of the tree is always the maximum element in the collection.
The algorithm is as follows:
- Create a max-heap. In Java, this can be done with
new PriorityQueue<>(Collections.reverseOrder()). - Insert all the stone weights from the input array into the max-heap. Building the heap takes
O(n log n)time. - Loop as long as the heap contains more than one element.
- In each iteration, extract the two largest elements by calling the
poll()method twice. Let these bey(the largest) andx(the second largest). - If
yis greater thanx, a new stone with weighty - xis formed. Insert this new weight back into the heap. - If
xandyare equal, both are destroyed, and nothing is added back to the heap.
When the loop finishes, the heap will have at most one stone. If the heap is empty, we return 0. Otherwise, we return the weight of the single remaining stone, which is at the top of the heap.
import java.util.Collections;import java.util.PriorityQueue; class Solution { public int lastStoneWeight(int[] stones) { // Create a max-heap by providing a reverse order comparator. PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder()); // Add all stones to the max-heap. for (int stone : stones) { maxHeap.add(stone); } // While there are at least two stones... while (maxHeap.size() > 1) { // Get the two heaviest stones. int y = maxHeap.poll(); int x = maxHeap.poll(); // If they are not equal, smash them and add the remainder. if (y > x) { maxHeap.add(y - x); } // If they are equal, they are both destroyed. } // If there is a stone left, return its weight, otherwise return 0. return maxHeap.isEmpty() ? 0 : maxHeap.peek(); }}Complexity
Time
O(n log n). Building the heap by adding `n` elements takes `O(n log n)`. The loop runs `n-1` times, and each iteration involves two `poll` operations and at most one `add` operation, each taking `O(log k)` time where `k` is the current heap size. This gives a total time complexity of `O(n log n)`.
Space
O(n) to store the `n` stones in the priority queue.
Trade-offs
Pros
This is the most efficient and standard solution for this type of problem.
The code is clean, easy to reason about, and performs well for any
n.
Cons
Requires knowledge of the Priority Queue / Heap data structure.
Solutions
Solution
class Solution: def lastStoneWeight(self, stones: List[int]) -> int: h = [- x for x in stones] heapify(h) while len(h) > 1: y, x = - heappop(h), - heappop(h) if x != y: heappush(h, x - y) return 0 if not h else - h[0]class Solution {public int lastStoneWeight(int[] stones) { PriorityQueue<Integer> q = new PriorityQueue<>((a, b)->b - a); for (int x : stones) { q.offer(x); } while (q.size() > 1) { int y = q.poll(); int x = q.poll(); if (x != y) { q.offer(y - x); } } return q.isEmpty() ? 0 : q.poll(); }}/** * @param {number[]} stones * @return {number} */ var lastStoneWeight = function (stones) { const pq = new MaxPriorityQueue(); for (const x of stones) { pq.enqueue(x); } while (pq.size() > 1) { const y = pq.dequeue()[" priority "]; const x = pq.dequeue()[" priority "]; if (x != y) { pq.enqueue(y - x); } } return pq.isEmpty() ? 0 : pq.dequeue()[" priority "]; };class Solution {public: int lastStoneWeight(vector<int> &stones) { priority_queue<int> pq; for (int x : stones) { pq.push(x); } while (pq.size() > 1) { int y = pq.top(); pq.pop(); int x = pq.top(); pq.pop(); if (x != y) { pq.push(y - x); } } return pq.empty() ? 0 : pq.top(); }};Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.