You are given an array of integers stones
where stones[i]
is the weight of the i<sup>th</sup>
stone. We are playing a game with the stones. On each turn, we choose any two stones and smash them together. Suppose the stones have weights x
and y
with x <= y
. The result of this smash is:
x == y
, both stones are destroyed, andx != y
, the stone of weight x
is destroyed, and the stone of weight y
has new weight y - x
.At the end of the game, there is at most one stone left.
Return the smallest possible weight of the left stone. If there are no stones left, return 0
.
Example 1:
Input: stones = [2,7,4,1,8,1]
Output: 1
Explanation:
We can combine 2 and 4 to get 2, so the array converts to [2,7,1,8,1] then,
we can combine 7 and 8 to get 1, so the array converts to [2,1,1,1] then,
we can combine 2 and 1 to get 1, so the array converts to [1,1,1] then,
we can combine 1 and 1 to get 0, so the array converts to [1], then that's the optimal value.
Example 2:
Input: stones = [31,26,33,21,40]
Output: 5
Constraints:
1 <= stones.length <= 30
1 <= stones[i] <= 100
Here's how to solve the stone smashing problem using dynamic programming.
1. Naive Approach (Recursion)
A straightforward, but inefficient, way to solve this problem is to use recursion. We try all possible pairs of stones, simulate the smash, and recursively call the function with the updated stones array. We keep track of the minimum possible weight at the end.
def lastStoneWeightRecursive(stones):
if not stones:
return 0
if len(stones) == 1:
return stones[0]
min_weight = float('inf')
for i in range(len(stones)):
for j in range(i + 1, len(stones)):
x = stones[i]
y = stones[j]
new_stones = stones[:i] + stones[i+1:j] + stones[j+1:]
if x == y:
min_weight = min(min_weight, lastStoneWeightRecursive(new_stones))
else:
new_stones.append(abs(x - y))
min_weight = min(min_weight, lastStoneWeightRecursive(new_stones))
return min_weight
This approach has exponential time complexity because of the overlapping subproblems. It will result in TLE (Time Limit Exceeded) for larger input sizes.
2. Optimal Approach (Dynamic Programming)
We can optimize the solution using dynamic programming. The key idea is to realize that we are essentially trying to divide the stones into two groups such that the absolute difference of their sums is minimized. This can be framed as a 0/1 knapsack problem.
Here's the algorithm:
total_sum
).dp
of size (total_sum // 2) + 1
. dp[i]
will be True if a subset of stones can sum up to i
, and False otherwise.dp[0]
to True (an empty set can sum up to 0).stones
array. For each stone stone
, iterate through the dp
table from total_sum // 2
down to stone
. If dp[j - stone]
is True, then dp[j]
becomes True (meaning we can achieve sum j
by including stone
).i
such that dp[i]
is True. This represents the sum of one group of stones.total_sum - 2 * i
(the difference between the two groups).def lastStoneWeightDP(stones):
total_sum = sum(stones)
target = total_sum // 2
dp = [False] * (target + 1)
dp[0] = True
for stone in stones:
for j in range(target, stone - 1, -1):
dp[j] = dp[j] or dp[j - stone]
for i in range(target, -1, -1):
if dp[i]:
return total_sum - 2 * i
3. Code Implementation (Python)
def lastStoneWeight(stones):
total_sum = sum(stones)
n = len(stones)
target = total_sum // 2
dp = [[False] * (target + 1) for _ in range(n + 1)]
for i in range(n + 1):
dp[i][0] = True
for i in range(1, n + 1):
for j in range(1, target + 1):
if stones[i-1] <= j:
dp[i][j] = dp[i-1][j] or dp[i-1][j - stones[i-1]]
else:
dp[i][j] = dp[i-1][j]
ans = 0
for j in range(target, -1, -1):
if dp[n][j]:
ans = total_sum - 2 * j
break
return ans
4. Big O Analysis
5. Edge Cases
stones
array is empty, the function should return 0.Example Usage
stones1 = [2, 7, 4, 1, 8, 1]
print(lastStoneWeight(stones1)) # Output: 1
stones2 = [31, 26, 33, 21, 40]
print(lastStoneWeight(stones2)) # Output: 5