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:
x and y and smash them togethersx == y, both stones are destroyedx < y, the stone of weight x is destroyed, and the stone of weight y has new weight y - 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
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.
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.
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.
Before attempting this problem, you should be comfortable with:
You always need to smash the two heaviest stones together.
A simple way to ensure this is:
Sorting each time is not the most efficient approach, but it is straightforward and easy to implement.
While the number of stones is greater than 1:
a = largest stoneb = second largest stonedifference = a - bdifference > 0, insert the new stone back into the list.If the list is empty, return 0
Else return the remaining stone.
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:
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.
n be the current number of stones.n > 1:cur = heaviest1 - heaviest2.n by 2 (since we used two stones).cur > 0:n elements to find the position pos where cur should be inserted to keep the array sorted.n by 1.pos one step to the right.cur at index pos.n == 1, return the remaining stone.n == 0, return 0.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 0We 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:
0.x into -x and build a min-heap.a and b (these represent the two heaviest stones).a != b, compute the remaining stone weight:diff = a - b (still negative)diff back into the heap.0.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])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:
bucket[w] store how many stones of weight w we have.a and b:a == b, they cancel out.a - b gets added back into the bucket.This works because bucket operations (increment, decrement, scanning) are efficient when the weight range is manageable.
bucket of size maxWeight + 1.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 firstWhere is the length of the array and is the maximum value in the array.
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.
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.