Stone Game III is a game-theory DP. Two players play optimally and you must predict who wins. The trick that makes it tractable is to stop tracking two separate scores and track a single number instead: the score difference the player to move can force.
Problem. There is a row of stones with integer values stones. Alice and Bob alternate turns,
Alice first. On each turn the current player takes the first 1, 2, or 3 stones from the front. Both
play to maximize their own total. Return "Alice", "Bob", or "Tie".
Example: stones = [1, 2, 3, 7] → answer "Bob". Whatever Alice grabs first, Bob can leave himself the 7.
The slow way first
The naive idea is to simulate every choice both players could make, branching 3 ways each turn. That is exponential — O(3^n) — and recomputes the same suffixes again and again.
The question to ask: what does a player actually care about? Not the two totals separately, but the gap between them. If from some position the player to move can force a final lead of d, that single number captures everything about that position.
The idea: track the score difference
Let dp[i] be the best (my total − opponent total) the player to move can guarantee using only stones[i:]. When it is your turn at i, you take k stones (k in 1..3). You collect their sum, and then your opponent is the one to move from i + k — so from your point of view their forced difference dp[i + k] counts against you.
So dp[i] = max over k in 1..3 of (sum(stones[i..i+k-1]) − dp[i+k]). We fill dp right-to-left from the base case dp[n] = 0. At the end, dp[0] is Alice minus Bob: positive → Alice, negative → Bob, zero → Tie.
Walk through it
Step through the animation. The pointer i moves right-to-left. At each spot we try taking 1, 2, or 3 stones, subtract the opponent's already-computed dp[i+k], and keep the best. For [1, 2, 3, 7] we get dp = [-1, 12, 10, 7, 0]. Since dp[0] = -1 < 0, Bob wins.
Pseudocode
n = number of stones
dp = array of size n+1, all zero # dp[n] = 0 base case
for i from n-1 down to 0:
take = 0
best = -infinity
for k in 1..3:
if i + k > n: stop trying larger k
take = take + stones[i + k - 1] # running sum of next k stones
best = max(best, take - dp[i + k])
dp[i] = best
if dp[0] > 0: return "Alice"
if dp[0] < 0: return "Bob"
return "Tie"The Python solution
def stone_game_iii(stones):
n = len(stones)
dp = [0] * (n + 1)
for i in range(n - 1, -1, -1):
take = 0
best = float("-inf")
for k in range(1, 4):
if i + k > n:
break
take += stones[i + k - 1]
best = max(best, take - dp[i + k])
dp[i] = best
if dp[0] > 0:
return "Alice"
if dp[0] < 0:
return "Bob"
return "Tie"dphas lengthn + 1;dp[n] = 0is the base case — no stones left, no difference to force.- We loop
ibackwards sodp[i + k]is always already computed before we need it. takeis the running sum of the nextkstones, so we never re-add them.take - dp[i + k]is the heart: I gaintake, but the opponent then forcesdp[i + k]against me.dp[i] = bestrecords the best difference the mover can guarantee fromi.- The sign of
dp[0]decides the winner — it is Alice minus Bob under optimal play.
Complexity
| Case | Time | Notes |
|---|---|---|
| Brute-force branching | O(3^n) (moderate) | every choice both players could make |
| DP (this solution) | O(n) (moderate) | n positions, constant work each |
O(n) (moderate)The inner loop runs at most 3 times, so each of the n positions is constant work: overall O(n) time and O(n) space for the dp array (reducible to O(1) since only the next 3 entries are needed).
When this pattern shows up
In two-player optimal-play problems, do not track both scores. Track the difference the player to move can force, and subtract the opponent's value of the resulting state. This single-number trick turns game theory into ordinary suffix DP.
Get the recurrence sign right: it is take - dp[i + k], not +. After you take k stones the other
player moves, and whatever difference they force counts against you. Forgetting the minus sign is the
classic bug here.
Practice
For stones = [1, 2, 3, 7], we already know dp[1] = 12, dp[2] = 10, dp[3] = 7. What is dp[0]?
1. What does dp[i] represent?
2. Why is the recurrence take - dp[i + k] rather than take + dp[i + k]?
3. Why do we fill dp from i = n-1 down to 0?
4. For stones = [1, 2, 3, 7], dp[0] = -1. Who wins?