Loading problems...
TL;DR: To solve this problem efficiently, use a Max-Heap to repeatedly extract and process the two heaviest stones until one or zero remain.
The problem asks us to simulate a game involving a collection of stones, each having a specific weight. In every round, we must select the two heaviest stones and smash them together. The outcome depends on their weights:
We continue this process until there is at most one stone left. We must return the weight of the remaining stone or 0 if none exist. This is a popular interview question that tests your ability to manage dynamic datasets where the maximum element is constantly required.
The naive approach attempts to simulate the process directly using a standard array or list. Since we always need the two heaviest stones, we can sort the array to find them.
y).x).x != y, calculate new_weight = y - x and insert it back into the array.textfunction lastStoneWeight(stones): while stones.length > 1: stones.sort() y = stones.pop() x = stones.pop() if x != y: stones.append(y - x) return stones[0] if stones.length == 1 else 0
while loop, which runs approximately times (where is the number of stones), we perform a sort operation. Sorting takes . Therefore, the total time complexity is .The inefficiency of the brute force method lies in re-sorting the entire array after every "smash" operation. We only change one value (inserting the remainder), but we pay the cost of sorting everything.
The Max-Heap data structure is designed specifically for this scenario. It maintains a partial order where the root node is always the maximum element.
By using a Max-Heap, we reduce the cost of the main loop operation from (sorting) to (heapify).
Visual Description: Imagine the stones organized in a binary tree structure. The invariant enforced is that every parent node is heavier than or equal to its children. The root of this tree holds the heaviest stone. When we extract the root, the last node in the tree moves to the root position and "sifts down" to restore the order. We do this twice to get the two heaviest stones. If a new stone is produced (the difference), we insert it at the bottom and let it "bubble up" to its correct position based on weight.

Let's trace the algorithm with stones = [2, 7, 4, 1, 8, 1].
{8, 7, 4, 2, 1, 1}.8 (Heaviest).7 (Second heaviest).8 - 7 = 1.1 back into heap.{4, 2, 1, 1, 1}.4.2.4 - 2 = 2.2 back into heap.{2, 1, 1, 1}.2.1.2 - 1 = 1.1 back into heap.{1, 1, 1}.1.1.1 - 1 = 0.{1}.1.The correctness relies on the Max-Heap invariant. At any step k, the algorithm requires the two largest elements and from the current set . The Max-Heap guarantees that the first pop() returns and the second pop() returns .
The logic strictly follows the problem statement:
Since the number of stones strictly decreases in every iteration, the loop is guaranteed to terminate. When it terminates, the remaining state represents the result of the simulation.
cpp#include <vector> #include <queue> class Solution { public: int lastStoneWeight(std::vector<int>& stones) { // Use a Max-Heap (priority_queue defaults to max-heap in C++) std::priority_queue<int> maxHeap(stones.begin(), stones.end()); while (maxHeap.size() > 1) { // Extract the two heaviest stones int y = maxHeap.top(); maxHeap.pop(); int x = maxHeap.top(); maxHeap.pop(); // If they are not equal, push the difference back if (x != y) { maxHeap.push(y - x); } } // If heap is empty return 0, otherwise return the last stone return maxHeap.empty() ? 0 : maxHeap.top(); } };
javaimport java.util.PriorityQueue; import java.util.Collections; class Solution { public int lastStoneWeight(int[] stones) { // Java's PriorityQueue is a Min-Heap by default. // Use Collections.reverseOrder() to make it a Max-Heap. PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder()); for (int stone : stones) { maxHeap.add(stone); } while (maxHeap.size() > 1) { // Extract the two heaviest stones int y = maxHeap.poll(); int x = maxHeap.poll(); // If weights differ, add the remaining piece back if (y != x) { maxHeap.add(y - x); } } return maxHeap.isEmpty() ? 0 : maxHeap.peek(); } }
pythonimport heapq class Solution: def lastStoneWeight(self, stones: list[int]) -> int: # Python's heapq is a Min-Heap. # We negate values to simulate a Max-Heap. max_heap = [-s for s in stones] heapq.heapify(max_heap) while len(max_heap) > 1: # Pop the heaviest (most negative) y = -heapq.heappop(max_heap) # Pop the second heaviest x = -heapq.heappop(max_heap) if x != y: # Push the negative difference back heapq.heappush(max_heap, -(y - x)) return -max_heap[0] if max_heap else 0
Let be the number of stones.
Time Complexity:
Space Complexity:
The Heap - Top K Elements pattern is versatile. Understanding how to maintain access to the "best" or "largest" elements dynamically is key to solving several other problems:
Why is my Python solution returning negative numbers?
-weight so that larger weights appear "smaller" and bubble to the top.-result instead of result.Why does my code crash on empty input or specific cases?
heap[0] or top() on an empty structure throws an exception. Always check or size before returning.isEmpty()Why can't I just sort the array once?
y - x back into the array, it might belong in the middle of the array, not necessarily at the end or beginning. The sorted order is broken.