1046. Last Stone Weight - Explanation
Description
You are given an array of integers stones where stones[i] represents the weight of the ith stone.
We want to run a simulation on the stones as follows:
- At each step we choose the two heaviest stones, with weight
xandyand smash them togethers - If
x == y, both stones are destroyed - If
x < y, the stone of weightxis destroyed, and the stone of weightyhas new weighty - x.
Continue the simulation until there is no more than one stone remaining.
Return the weight of the last remaining stone or return 0 if none remain.
Example 1:
Input: stones = [2,3,6,2,4]
Output: 1Explanation:
We smash 6 and 4 and are left with a 2, so the array becomes [2,3,2,2].
We smash 3 and 2 and are left with a 1, so the array becomes [1,2,2].
We smash 2 and 2, so the array becomes [1].
Example 2:
Input: stones = [1,2]
Output: 1Constraints:
1 <= stones.length <= 201 <= stones[i] <= 100
Topics
Recommended Time & Space Complexity
You should aim for a solution as good or better than O(nlogn) time and O(n) space, where n is the size of the input array.
Hint 1
A naive solution would involve simulating the process by sorting the array at each step and processing the top 2 heaviest stones, resulting in an O(n * nlogn) time complexity. Can you think of a better way? Consider using a data structure that efficiently supports insertion and removal of elements and maintain the sorted order.
Hint 2
We can use a Max-Heap, which allows us to retrieve the maximum element in O(1) time. We initially insert all the weights into the Max-Heap, which takes O(logn) time per insertion. We then simulate the process until only one or no element remains in the Max-Heap. At each step, we pop two elements from the Max-Heap which takes O(logn) time. If they are equal, we do not insert anything back into the heap and continue. Otherwise, we insert the difference of the two elements back into the heap.
Prerequisites
Before attempting this problem, you should be comfortable with:
- Heap / Priority Queue - Efficiently retrieving and removing the maximum (or minimum) element
- Sorting - Sorting arrays to process elements in order
- Simulation - Implementing step-by-step processes as described in problem statements
1. Sorting
Intuition
You always need to smash the two heaviest stones together.
A simple way to ensure this is:
- Sort the list of stones so the heaviest stones are at the end.
- Remove the last two stones (the largest values).
- Smash them:
- If they are equal → both disappear.
- If they are different → the difference becomes a new stone.
- Insert the new stone (if any) back into the list.
- Repeat until at most one stone remains.
Sorting each time is not the most efficient approach, but it is straightforward and easy to implement.
Algorithm
While the number of stones is greater than
1:- Sort the stones in increasing order.
- Remove the two largest stones:
- Let
a= largest stone - Let
b= second largest stone
- Let
- Compute
difference = a - b - If
difference > 0, insert the new stone back into the list.
If the list is empty, return
0
Else return the remaining stone.
Time & Space Complexity
- Time complexity:
- Space complexity: or depending on the sorting algorithm.
2. Binary Search
Intuition
We always smash the two heaviest stones.
If we keep the stones in sorted order, the two heaviest are at the end of the array, so we can pick them easily.
After smashing:
- We remove the two heaviest stones.
- If they are different, their difference becomes a new stone.
- To keep the array sorted, we need to insert this new stone at the correct position.
Instead of scanning linearly to find the position, we use binary search to quickly find where this new stone should go in the sorted list, and then shift elements to insert it there. This keeps the list sorted for the next iteration.
Algorithm
- Sort the array of stones in non-decreasing order.
- Let
nbe the current number of stones. - While
n > 1:- Take the last two stones (the two heaviest).
- Compute
cur = heaviest1 - heaviest2. - Decrease
nby2(since we used two stones). - If
cur > 0:- Use binary search on the first
nelements to find the positionposwherecurshould be inserted to keep the array sorted. - Increase
nby1. - Shift elements from the end to
posone step to the right. - Place
curat indexpos.
- Use binary search on the first
- After the loop:
- If
n == 1, return the remaining stone. - If
n == 0, return0.
- If
class Solution:
def lastStoneWeight(self, stones: List[int]) -> int:
stones.sort()
n = len(stones)
while n > 1:
cur = stones.pop() - stones.pop()
n -= 2
if cur > 0:
l, r = 0, n
while l < r:
mid = (l + r) // 2
if stones[mid] < cur:
l = mid + 1
else:
r = mid
pos = l
n += 1
stones.append(0)
for i in range(n - 1, pos, -1):
stones[i] = stones[i - 1]
stones[pos] = cur
return stones[0] if n > 0 else 0Time & Space Complexity
- Time complexity:
- Space complexity: or depending on the sorting algorithm.
3. Heap
Intuition
We always need to repeatedly remove the two heaviest stones.
A max-heap is perfect for this because it lets us efficiently extract the largest values.
Most languages provide min-heaps, so a common trick is to store negative values.
This makes the smallest (most negative) value represent the largest stone.
Process:
- Convert all stones to negative and build a heap.
- Repeatedly pop the two smallest (i.e., the two heaviest stones).
- Smash them:
- If equal → both are destroyed.
- If different → push the negative of their difference back into the heap.
- When one or zero stones remain, return the remaining weight or
0.
Algorithm
- Convert every stone weight
xinto-xand build a min-heap. - While the heap contains more than one stone:
- Pop two values
aandb(these represent the two heaviest stones). - If
a != b, compute the remaining stone weight:diff = a - b(still negative)- Push
diffback into the heap.
- Pop two values
- After the loop:
- If the heap is empty, return
0. - Otherwise return the absolute value of the remaining stone.
- If the heap is empty, return
class Solution:
def lastStoneWeight(self, stones: List[int]) -> int:
stones = [-s for s in stones]
heapq.heapify(stones)
while len(stones) > 1:
first = heapq.heappop(stones)
second = heapq.heappop(stones)
if second > first:
heapq.heappush(stones, first - second)
stones.append(0)
return abs(stones[0])Time & Space Complexity
- Time complexity:
- Space complexity:
4. Bucket Sort
Intuition
Since all stone values lie within a limited numeric range, we can avoid sorting or using a heap by using bucket sort / frequency counting.
Instead of tracking every stone individually, we store how many stones exist for each possible weight.
Key ideas:
- Let
bucket[w]store how many stones of weightwwe have. - We repeatedly look for the heaviest available stone.
- When smashing stones of weights
aandb:- If
a == b, they cancel out. - If different, the leftover stone
a - bgets added back into the bucket.
- If
- We continue until only one non-zero weight remains.
This works because bucket operations (increment, decrement, scanning) are efficient when the weight range is manageable.
Algorithm
- Find the maximum stone weight.
- Create a frequency array
bucketof sizemaxWeight + 1. - Count how many stones of each weight exist.
- Start from the heaviest weight and repeat:
- If the count at this weight is even, all stones cancel in pairs.
- If odd, one stone remains; find the next heaviest stone to smash with it.
- Compute the difference and update the bucket.
- Move the pointer to the next heaviest relevant weight.
- When only one weight remains with an odd count, return it.
- If all stones cancel out, return
0.
class Solution:
def lastStoneWeight(self, stones: List[int]) -> int:
maxStone = max(stones)
bucket = [0] * (maxStone + 1)
for stone in stones:
bucket[stone] += 1
first = second = maxStone
while first > 0:
if bucket[first] % 2 == 0:
first -= 1
continue
j = min(first - 1, second)
while j > 0 and bucket[j] == 0:
j -= 1
if j == 0:
return first
second = j
bucket[first] -= 1
bucket[second] -= 1
bucket[first - second] += 1
first = max(first - second, second)
return firstTime & Space Complexity
- Time complexity:
- Space complexity:
Where is the length of the array and is the maximum value in the array.
Common Pitfalls
Using a Min-Heap Instead of Max-Heap
Many languages provide min-heaps by default (like Python's heapq). You need to either use a max-heap or negate the values when inserting and extracting. Forgetting this results in smashing the two smallest stones instead of the two heaviest.
Forgetting to Handle the Empty Result Case
After all smashing is complete, the stones array could be empty (all stones canceled out). Returning stones[0] without checking if the array is empty causes an index error. Always check if the result is empty and return 0 in that case.
Sign in to join the discussion