AlgoMaster Logo

Last Stone Weight II

mediumFrequency7 min readUpdated June 23, 2026

Understanding the Problem

This looks like a simulation problem: smash stones in some order and find the minimum leftover. But "any two stones" on each turn means the number of possible orderings is enormous, and greedily picking the two heaviest (the strategy that works for Last Stone Weight I) does not give the minimum result here.

Every smash subtracts one stone's weight from another. Tracing through any sequence of smashes, each stone's original weight ends up either added with a + sign or a - sign in the final expression. For example, with stones [2, 7, 4, 1, 8, 1], one valid sequence produces (+8) + (-7) + (-4) + (+2) + (-1) + (+1) = -1, and the answer is the absolute value, 1.

So the problem reduces to partitioning the stones into two groups so the difference between the group sums is minimized. If group 1 sums to S1 and group 2 sums to S2 with S1 >= S2, the answer is S1 - S2. Since S1 + S2 = totalSum, we want S2 to be as close to totalSum / 2 as possible. This is the 0/1 Knapsack problem with capacity totalSum / 2.

Key Constraints:

  • 1 <= stones.length <= 30. With n up to 30, an O(2^n) brute force does about 10^9 sign assignments, too slow. An O(n * sum) DP, with sum at most 3000, runs in well under a millisecond.
  • 1 <= stones[i] <= 100. The maximum total sum is 30 100 = 3000, so target is at most 1500 and the DP table has at most 31 1501 cells. The values fit comfortably in a 32-bit integer, so there is no overflow concern.

Approach 1: Brute Force (Recursion)

Intuition

Try every way to assign each stone a + or - sign. Each stone belongs to one of two groups, so there are 2^n assignments. For each one, compute the difference between the two group sums and track the minimum.

Recursion expresses this directly: at each stone, put it in group 1 or group 2, then recurse on the remaining stones.

Algorithm

  1. Compute the total sum of all stones.
  2. Use a recursive function that processes one stone at a time.
  3. At each stone, try two choices: include it in the "subtract" group (add to current sum) or exclude it (skip to the next stone).
  4. When all stones are processed, the result is totalSum - 2 * currentSum. Track the minimum absolute result across all recursive calls.

Example Walkthrough

Input:

0
2
1
7
2
4
3
1
4
8
5
1
stones

The total sum is 23, so minResult starts at 23. The recursion explores all 2^6 = 64 sign assignments, where currentSum accumulates the weights placed in group 2, and each leaf computes |23 - 2 * currentSum|. A few representative leaves:

  • All stones in group 1: currentSum = 0, diff = |23 - 0| = 23.
  • Group 2 = {2}: currentSum = 2, diff = |23 - 4| = 19.
  • Group 2 = {7, 4, 1}: currentSum = 12, diff = |23 - 24| = 1. This updates minResult to 1.
  • Group 2 = {2, 8, 1}: currentSum = 11, diff = |23 - 22| = 1. Ties the current best.

No partition can do better, because 23 is odd and the closest a subset sum can get to 11.5 is 11 or 12, both giving a difference of 1. After all 64 leaves are visited, minResult is 1, which is returned.

Code

The brute force tries every partition, but many recursive paths reach the same (index, currentSum) state and recompute it. Dynamic programming removes that redundancy by tracking which subset sums are reachable rather than enumerating every path.

Approach 2: Dynamic Programming (2D)

Intuition

Since the problem reduces to "find a subset with sum as close to totalSum / 2 as possible," we can use a 2D DP table. Define dp[i][j] as whether it is possible to achieve a subset sum of exactly j using the first i stones. For each stone, we either include it in the subset or not. After filling the table, we scan for the largest j <= totalSum / 2 where dp[n][j] is true, and the answer is totalSum - 2 * j.

This is the standard 0/1 Knapsack formulation. The "capacity" is totalSum / 2, and each stone's weight equals its value. We are trying to fill the knapsack as full as possible.

Algorithm

  1. Compute totalSum and set target = totalSum / 2.
  2. Create a 2D boolean array dp[n+1][target+1]. Set dp[0][0] = true (zero stones, zero sum is achievable).
  3. For each stone i from 1 to n:
    • For each sum j from 0 to target:
      • dp[i][j] = dp[i-1][j] (skip this stone)
      • If j >= stones[i-1], also check dp[i][j] |= dp[i-1][j - stones[i-1]] (include this stone)
  4. Find the largest j where dp[n][j] is true.
  5. Return totalSum - 2 * j.

Example Walkthrough

1Initial: only sum 0 is achievable
0
true
1
false
2
false
3
false
4
false
5
false
6
false
7
false
8
false
9
false
10
false
11
false
1/7

Code

The 2D DP works, but each row only depends on the previous row. We can compress it to a single 1D array.

Approach 3: Dynamic Programming (1D Space-Optimized)

Intuition

Since dp[i][j] only depends on dp[i-1][j] and dp[i-1][j - stones[i-1]], the 2D table collapses into a single 1D array. The one constraint is to iterate j from right to left, from target down to stone. When dp[j] is updated, dp[j - stone] must still hold the value from before the current stone was processed, and right-to-left order guarantees that, because lower indices are visited after higher ones.

Iterating left to right would read dp[j - stone] after it had already been updated for the current stone, which lets a single stone contribute more than once. That is the unbounded knapsack recurrence. Right-to-left order keeps each stone usable at most once, preserving the 0/1 constraint.

Algorithm

  1. Compute totalSum and set target = totalSum / 2.
  2. Create a 1D boolean array dp[target+1]. Set dp[0] = true.
  3. For each stone:
    • Iterate j from target down to stone (right to left).
    • Set dp[j] = dp[j] || dp[j - stone].
  4. Find the largest j where dp[j] is true.
  5. Return totalSum - 2 * j.

Example Walkthrough

1dp[0]=true (empty subset has sum 0)
0
true
1
false
2
false
3
false
4
false
5
false
6
false
7
false
8
false
9
false
10
false
11
false
1/8

Code