Imagine you and a friend find a row of stone piles. Each pile has some value. You alternate turns. On each turn, you can take stones from either end of the row. Both players are optimal — they play to maximize their own score and minimize yours. Who wins?
This is Stone Game I. It looks simple, but it teaches you the ENTIRE minimax DP framework. Every subsequent Stone Game problem (II through IX) is just a variation: change how many stones you can take, change the scoring, add a multiplier, or change the move rules entirely.
The unifying insight across ALL stone game problems:
Tip
💡 The One Pattern to Rule Them All: "At my turn, I choose an action (pick 1, pick X, split the array, etc.). My score = sumOfWhatITook + (remainingTotal - opponentScoreOnRemaining)." This is the minimax recurrence — every stone game uses it.
Let's trace through the entire series, from the simplest to the most complex.
1. Stone Game I & II — The Foundation
Stone Game I — Pick from Ends
Problem: Alice and Bob play a game with a row of stones. Each pile has some value. Players alternate, taking one pile from either end. The player with the higher total score at the end wins. Both play optimally. Determine if Alice (first player) can win.
Note
🎪 Real-World Analogy: You and your friend are at a buffet line. There's a row of dishes with different value ratings. You take turns picking a dish from either end of the row. You want the highest total value. Your friend also wants the highest total value. What do you pick? You need to think: "If I take this dish, what options remain for my friend, and how much will I get later?"
🤔 Striver-Style Thinking — "At Each Position, I Either Pick Left or Right"
Let's frame this recursively. I'm at subarray piles[i..j]. I have TWO choices:
But wait — why SUBTRACT? Because dp[i+1][j] is the OPPONENT's advantage from that subarray. If I pick piles[i], the opponent gets dp[i+1][j] more than me from the remaining game. So my net advantage = what I just took - opponent's advantage on rest.
Key Insight
🔑 The Crucial Insight: dp[i][j] represents the net score advantage(myScore - opponentScore) that the current player can achieve from subarray i..j. This is positive = current player wins, negative = other player wins.
📊 Dry Run — piles = [3, 9, 1, 4, 7, 2]
Let's trace the DP table from smallest subarrays to the full array:
i
j
Subarray
Pick Left
Pick Right
dp[i][j]
What Happened?
0
0
[3]
3 — 0
3 — 0
3
Only one stone, take it
1
1
[9]
9 — 0
9 — 0
9
Only one stone, take it
0
1
[3, 9]
3 — dp[1][1] = 3 — 9 = -6
9 — dp[0][0] = 9 — 3 = 6
6
Pick right (9) wins! Net advantage = 6
2
2
[1]
1 — 0
1 — 0
1
Base case
1
2
[9, 1]
9 — dp[2][2] = 9 — 1 = 8
1 — dp[1][1] = 1 — 9 = -8
8
Pick left (9) wins
0
2
[3, 9, 1]
3 — dp[1][2] = 3 — 8 = -5
1 — dp[0][1] = 1 — 6 = -5
-5
Either way loses, but -5 is best
3
3
[4]
4 — 0
4 — 0
4
Base case
2
3
[1, 4]
1 — dp[3][3] = 1 — 4 = -3
4 — dp[2][2] = 4 — 1 = 3
3
Pick right (4)
1
3
[9, 1, 4]
9 — dp[2][3] = 9 — 3 = 6
4 — dp[1][2] = 4 — 8 = -4
6
Pick left (9)
0
3
[3, 9, 1, 4]
3 — dp[1][3] = 3 — 6 = -3
4 — dp[0][2] = 4 — (-5) = 9
9
Pick right (4)!
4
4
[7]
7 — 0
7 — 0
7
Base case
3
4
[4, 7]
4 — dp[4][4] = 4 — 7 = -3
7 — dp[3][3] = 7 — 4 = 3
3
Pick right (7)
2
4
[1, 4, 7]
1 — dp[3][4] = 1 — 3 = -2
7 — dp[2][3] = 7 — 3 = 4
4
Pick right (7)
1
4
[9, 1, 4, 7]
9 — dp[2][4] = 9 — 4 = 5
7 — dp[1][3] = 7 — 6 = 1
5
Pick left (9)
0
4
[3, 9, 1, 4, 7]
3 — dp[1][4] = 3 — 5 = -2
7 — dp[0][3] = 7 — 9 = -2
-2
Losing position for current player
5
5
[2]
2 — 0
2 — 0
2
Base case
4
5
[7, 2]
7 — dp[5][5] = 7 — 2 = 5
2 — dp[4][4] = 2 — 7 = -5
5
Pick left (7)
3
5
[4, 7, 2]
4 — dp[4][5] = 4 — 5 = -1
2 — dp[3][4] = 2 — 3 = -1
-1
Losing for current player
2
5
[1, 4, 7, 2]
1 — dp[3][5] = 1 — (-1) = 2
2 — dp[2][4] = 2 — 4 = -2
2
Pick left (1)! Surprising
1
5
[9, 1, 4, 7, 2]
9 — dp[2][5] = 9 — 2 = 7
2 — dp[1][4] = 2 — 5 = -3
7
Pick left (9)
0
5
[3, 9, 1, 4, 7, 2]
3 — dp[1][5] = 3 — 7 = -4
2 — dp[0][4] = 2 — (-2) = 4
4
🔥 Alice (first) picks right (2), net advantage = 4 → Alice wins!
Implementation
Tech DOSE — Edge Cases
Single pile: Alice takes it, Alice wins. dp[0][0] = piles[0] > 0 → true.
Two piles [a, b]: Alice picks max(a, b), Bob gets the other. Net advantage = |a - b|. If a == b → net = 0 → Alice cannot win (but LeetCode guarantees odd count, so never a tie).
All equal values: Since n is odd per problem constraints, first player always wins by symmetry.
Large values (up to 10^7): DP works fine with 32-bit integers, but use BigInt if values can sum beyond 2^31.
n up to 500: O(n²) DP with 2D array = 250K entries, perfectly fine. For n up to 1000, use space-optimized 1D.
ProblemEmbed: stone-game
📋 The "Even n" Trap — LeetCode Special Constraint
LeetCode 877 has a special constraint: n is always EVEN, and total sum is ODD. This means there's NO TIE. And because Alice goes first with even n, she can always force a win by picking all even-indexed or all odd-indexed stones! This makes the problem trivially return true.
Warning
⚠️ Interview Trap: If the interviewer asks "Stone Game I" with even n, they might expect you to notice the mathematical shortcut. But ALWAYS implement the full minimax DP first, THEN mention the shortcut. Shows you understand both the general framework and the specific optimization.
Stone Game II — The M Multiplier
Problem: Now Alice can take 1 to 2*M stones from thebeginning (left side only, not both ends!) with each move. M starts at 1 and updates: if Alice takes X stones, M = max(M, X) for the next player. Who wins?
Note
🎪 Real-World Analogy: You're at a conveyor belt of sushi dishes. You can take 1 to 2*M plates from the front. The more plates you take, the bigger M becomes for the next person (they can take more). You want to maximize your total sushi value. But if you take too many, the next person can take even more!
🤔 Striver-Style Thinking — "At Position i With M, I Choose X = 1..2M"
"OK so I'm at position i, and the current multiplier is M. I can take X stones where X ranges from 1 to 2M. If I take X stones, I get the sum of piles[i..i+X-1]. The opponent then faces position i+X with new M = max(M, X). The remaining stones are all the stones from i+X to end. The opponent's optimal score = dp[i+X][newM]. So my total = sumIJustTook + (remainingTotal - opponentScore)."
📊 Dry Run — piles = [2, 7, 9, 4, 4]
i
M
Possible X
Taken
Remaining Total
Opponent Score
My Total
Best
4
any
1
4
0
0
4
4
3
1
1
4
4
4
4
4
3
2
1-2
4 or 8
4 or 0
4 or 0
4 or 8
8
2
1
1
9
8
8
9
9
2
2
1-2
9 or 13
8 or 4
8 or 4
9 or 13
13
1
1
1
7
17
13
11
11
1
2
1-2
7 or 16
17 or 8
13 or 8
11 or 16
16
0
1
1
2
24
16
10
—
0
1
2
9
17
13
13
13 ← Alice takes 2
Tip
⚡ Key Pattern: Stone Game II changes from "pick from ends" to "pick from front with variable count." The state expands from (i, j) to (i, M). The M multiplier adds a strategic dimension: taking more stones now gives opponent more power later. This is the minimax trade-offamplified.
ProblemEmbed: stone-game-ii
2. Stone Game III-V — Advanced DP Patterns
Stone Game III — Take 1/2/3 from Front
Problem: Take 1, 2, or 3 stones from the front each turn. Values can be NEGATIVE. Determine the winner using score comparison.
🤔 Striver-Style Thinking
"At position i, I have exactly 3 choices: take piles[i], take piles[i..i+1], or take piles[i..i+2]. For each, I get the sum of those stones. The opponent then faces position i+X (where X=1,2,3). The opponent's optimal score from there is dp[i+X]. My maximum total from i to end = max over X of (sum[i..i+X-1] + suffix[i+X] - dp[i+X]). Simplify: myTotal = totalFromHere - dp[i+X]. Wait — that means dp[i] = max(suffix[i] - dp[i+1], suffix[i] - dp[i+2], suffix[i] - dp[i+3])! No... Let's re-think."
Actually, the recurrence is cleaner than that. dp[i] = maximum score advantagethe current player can get from piles[i:]. If I take X stones worth sum, the opponent gets dp[i+X] advantage from the rest. My advantage = sum - opponentAdvantage. But sum + restTotal = suffix[i]. And opponent's total score from rest = dp[i+X]. So my total = sum + (suffix[i+X] - dp[i+X]) = suffix[i] - dp[i+X].
📊 Dry Run — values = [1, 2, 3, 7]
i
suffix[i]
Take 1
Take 2
Take 3
dp[i]
3
7
7 - dp[4]=7
—
—
7
2
10
10 - dp[3]=3
10 - dp[4]=10
—
10
1
12
12 - dp[2]=2
12 - dp[3]=5
12 - dp[4]=12
12
0
13
13 - dp[1]=1
13 - dp[2]=3
13 - dp[3]=6
6
Alice gets 6, Bob gets 13 — 6 = 7. Bob wins! 🏆
Key Insight
🔑 Pattern Recognition: Stone Game III uses 1D DP because you can only take from the front, not both ends. The suffix array precomputes "total remaining" so each transition is O(1). Total: O(n) time, O(n) space.
ProblemEmbed: stone-game-iii
Stone Game IV — Perfect Squares
Problem: There's a single pile of n stones. Each turn, a player removes a perfect square number of stones (1, 4, 9, 16, ...). The player who cannot move loses. Determine if Alice (first player) can force a win.
Note
🎪 Real-World Analogy: You have a pile of 100 chocolate bars. On each turn, you can take 1, 4, 9, 16, 25, 36, 49, 64, 81, or 100 bars (a perfect square). The player who takes the last bar wins. If you take 1, the other player faces 99. If you take 4, they face 96. Which move guarantees victory?
🤔 Striver-Style Thinking
"At state n, I can remove any perfect square ≤ n. If I can reach a state where the opponent LOSES, I win. This is the classic 'can I force a win?' DP pattern. dp[n] = true if there exists a square s such that dp[n-s] = false. Base: dp[0] = false (no stones left → current player lost)."
📊 Dry Run — n = 10
n
Squares ≤ n
Reachable States
Any Losing State?
dp[n]
0
—
—
—
❌
1
1
0
dp[0]=false ✅
✅
2
1
1
dp[1]=true ❌
❌
3
1
2
dp[2]=false ✅
✅
4
1, 4
3, 0
dp[0]=false ✅
✅
5
1, 4
4, 1
dp[4]=true, dp[1]=true ❌
❌
6
1, 4
5, 2
dp[5]=false ✅
✅
7
1, 4
6, 3
dp[6]=true, dp[3]=true ❌
❌
8
1, 4
7, 4
dp[7]=false ✅
✅
9
1, 4, 9
8, 5, 0
dp[0]=false ✅
✅
10
1, 4, 9
9, 6, 1
dp[9]=true, dp[6]=true, dp[1]=true ❌
❌ Alice loses!
Warning
⚠️ Pattern Shift: Stone Game IV is a single-pile, take-away game. It's not about scoring — it's about positional analysis (can I force a win?). This is closer to Nim than to the minimax DP of Stone Game I-III. The DP is boolean, not numeric: dp[i] = can current player force a win from i stones?
ProblemEmbed: stone-game-iv
Stone Game V — Split Array
Problem: An array of stones is split into two non-empty subarrays(left and right). The player keeps the subarray with the larger sum, and the opponent plays with the smaller subarray. The opponent then continues splitting their subarray until only one stone remains. If both subarrays have equal sum, the player can choose either side. Determine the maximum score Alice can achieve.
🤔 Striver-Style Thinking
"At subarray i..j, I compute the total sum. I try every possible split point k (i ≤ k < j). This divides into left = sum[i..k] and right = sum[k+1..j]. If left > right: I take left, opponent gets dp[k+1][j]. My score = left + (total - left - opponentScore)? No — I KEEP the left subarray as my score, and opponent GAMES on the right. Wait — the opponent plays with the smaller subarray. They continue splitting it. So I keep the larger side, and the opponent will get whatever score they can from the smaller side. My total = largerSum + (what I get from larger side after opponent plays? No...)
Actually the game is: at each round, split the current subarray. The current player keeps the side with larger sum (their score = that sum), and the opponent continues with the smaller side. So if left is larger, I get left sum, and opponent recursively plays on the right.
Tip
⚡ Optimization Insight: Stone Game V is O(n³) with naive sum calculation. Using prefix sums brings sum(i,j) to O(1), so total is O(n³) for the split loop. For n ≤ 500, this is acceptable (125M operations). For n ≤ 1000, needs optimization using the monotonic property of split points.
ProblemEmbed: stone-game-v
3. Stone Game VI-IX — Complex Strategies
Stone Game VI — Greedy with Two Arrays
Problem: Now each stone has TWO values: Alice's value (aliceValues[i]) and Bob's value (bobValues[i]). Alice picks first. When a player picks a stone, they get their OWN value for that stone. Both play optimally. Who wins?
Note
🎪 Real-World Analogy: You and your friend are bidding on items at an auction. Each item is worth different amounts to each of you. You take turns picking items. You want to maximize YOUR total value, but you also want to DENY high-value items to your friend. The key insight: an item worth 10 to Alice and 9 to Bob is worth picking for Alice (she gets 10 AND denies Bob 9). An item worth 5 to Alice and 10 to Bob is ALSO worth picking — she only gets 5, but she denies Bob 10!
🤔 Striver-Style Thinking
"This is NOT a DP problem! It's a greedy problem. The optimal strategy is to pick the stone with the HIGHEST sum of both values (aliceValue + bobValue). Why? Because when I pick a stone, I get MY value AND deny my opponent THEIR value. The total impact on the score difference is (myValue + opponentValue) — I gain myValue and opponent loses the chance to gain opponentValue. So I should pick the stone where this combined value is highest."
Sorted by combined: Stone 1 (combined=4) first, then Stone 0 (combined=3). Alice picks Stone 1 → Alice gets 3. Bob picks Stone 0 → Bob gets 2. Alice (3) > Bob (2) → Alice wins! If Alice had picked Stone 0: Alice gets 1, Bob picks Stone 1 → Bob gets 1. Score 1-1 → tie. But Alice wants to WIN, so picking the higher combined stone is optimal!
Key Insight
🔑 Key Insight: Stone Game VI shows that NOT all game theory problems require DP. When both players have different valuations for each item, the optimal strategy is to evaluate each item's total value (myValuation + opponentValuation). This captures both "I gain X" AND "I deny Y to opponent."
ProblemEmbed: stone-game-vi
Stone Game VII — Difference-Based Scoring
Problem: Similar to Stone Game I (pick from ends), but the scoring isdifferent: when you take a stone, your score = sum of REMAINING stones, not the value you took. Determine the maximum score difference(Alice - Bob) that Alice can achieve.
🤔 Striver-Style Thinking
"At subarray i..j, the current total sum is suffix[j+1] - suffix[i]. If I pick from left (piles[i]), my score for THIS turn = sum[i+1..j] (all remaining). Then opponent plays optimally on subarray i+1..j, getting dp[i+1][j] advantage over me. My net advantage = sum[i+1..j] - dp[i+1][j]. If I pick from right (piles[j]), my score = sum[i..j-1], opponent gets dp[i][j-1]. My net advantage = sum[i..j-1] - dp[i][j-1]."
📊 Dry Run — stones = [5, 3, 1, 4, 2]
i
j
Subarray
Pick Left Score
Pick Right Score
dp[i][j]
0
0
[5]
0
0
0
1
1
[3]
0
0
0
0
1
[5,3]
3 - dp[1][1]=3
5 - dp[0][0]=5
5
1
2
[3,1]
1 - 0 = 1
3 - 0 = 3
3
0
2
[5,3,1]
4 - dp[1][2]=1
8 - dp[0][1]=3
5
2
2
[1]
0
0
0
1
3
[3,1,4]
5 - dp[2][3]
4 - dp[1][2]
—
3
3
[4]
0
0
0
2
4
[1,4,2]
6 - dp[3][4]
5 - dp[2][3]
—
3
4
[4,2]
2 - 0 = 2
4 - 0 = 4
4
0
4
[5,3,1,4,2]
10 - dp[1][4]
13 - dp[0][3]
? (Answer: Alice advantage)
Key Insight
🔑 Difference from Stone Game I: In Stone Game I, you GET the value of the stone you take. In Stone Game VII, you GET the sum of ALL REMAINING stones (not the one you took). This flips the strategy — you want to leave your opponent a smaller total, so you might take a smaller edge stone to leave less for opponent.
ProblemEmbed: stone-game-vii
Stone Game VIII — Prefix Cut Game
Problem: Starting from the beginning, a player can take a prefixof the array (must be shorter than the current array). Their score = total value of that prefix. The opponent then plays on the remaining suffix. The player who takes the last stonegets a bonus: their score = the total of ALL remaining stones. Determine Alice's max score advantage.
🤔 Striver-Style Thinking
"This is similar to Stone Game II but instead of taking 1..2M stones, we can take ANY prefix of the current array (except the whole thing — unless it's the last move). The key constraint: you CAN'T take the entire array on your first move. You must leave at least 1 stone. But on your last move (when only 1 stone remains), you take it and get its value PLUS the bonus."
Tip
⚡ Optimization Trick: Stone Game VIII can be solved in O(n) time and O(1) space! The key insight is that the optimal strategy only needs to track the maximum difference seen so far while scanning from right to left. No 2D DP table needed.
ProblemEmbed: stone-game-viii
Stone Game IX — Modulo 3 Strategy
Problem: There's a pile of stones where each stone has a value. On each turn, a player removes a stone. If the sum of removed stonesis divisible by 3, the player who just moved loses (not the usual "can't move loses"). In other words: if after your move, the total sum of all removed stones so far is a multiple of 3, you LOSE. Determine if Alice can force a win.
Note
🎪 Real-World Analogy: You and your friend are playing Russian roulette with a counter. Each turn, you add a number to a running total. If the total becomes a multiple of 3, you LOSE. You can choose which number to add. The numbers are given — you just decide when to play which number. This isn't about maximizing score — it's about controlling the modulo state.
🤔 Striver-Style Thinking
"This is NOT about scores — it's about modulo classes. Every stone's value modulo 3 is either 0, 1, or 2. If I remove a stone with value % 3 == 0, it doesn't change the running sum's modulo — it's neutral. But if I remove a % 3 == 1 stone, it adds 1 to the sum, and a % 3 == 2 adds 2. The game state is entirely determined by how many 1s and 2s are remaining, and the current sum % 3. Since we only care about modulo 3, we count stones in each class."
📊 Example Analysis
stones
count0
count1
count2
count0 even?
Alice Wins?
Why?
[2]
0
0
1
✅
❌
min(0,1)=0 → Alice loses
[1, 2]
0
1
1
✅
✅
min(1,1)>0 → Alice wins
[3, 3, 1]
2
1
0
✅
❌
min(1,0)=0 → Alice loses
[5, 2, 2, 2]
0
1
3
✅
✅
min(1,3)>0 → Alice wins
Warning
⚠️ Completely Different Game: Stone Game IX is not about scoring or pick-from-ends. It's a modulo avoidance game — the losing condition is making the total sum divisible by 3. This requires a completely different analytical framework based on counting remainders and turn parity.
ProblemEmbed: stone-game-ix
Stone Division — Grundy-Based Nim Variant
Problem: There's a pile of n stones. A move consists of choosing adivisor d of n (where d > 0 and d < n) and splitting the pile intod piles of size n/d. If no divisor exists (n = 1), the player cannot move and loses. This is a splitting game, not a Nim heap! Determine if the first player can force a win.
🤔 Striver-Style Thinking
"This is a Grundy-based game. Each pile is an independent sub-game. When I split a pile of size n into d piles of size n/d, the Grundy number of the resulting state is the XOR of d copies of Grundy(n/d). By the XOR property, XOR of an even number of copies = 0, XOR of an odd number of copies = Grundy(n/d). So Grundy(n) = mex{Grundy(d piles of n/d)} = mex{(d % 2) * Grundy(n/d)}. Base: Grundy(1) = 0 (no moves)."
📊 Dry Run — Grundy Numbers
n
Divisors (< n)
Splits
XOR Values
mex
Grundy
1
—
—
—
0
0
2
1
1 pile of 2
1%2 * g(2)=1*0=0
1
1
3
1
1 pile of 3
1%2 * g(3)=1*?=?
? (recursive)
1
4
1, 2
1 pile of 4, 2 piles of 2
g(4)=?, 0
? (depends on g(4))
0
5
1
1 pile of 5
g(5) ... circular?
?
1
6
1, 2, 3
1 pile of 6, 2 piles of 3, 3 piles of 2
g(6), 0, 1*1=1
mex{g(6), 0, 1}
depends
Key Insight
🔑 Stone Division Pattern: For this specific problem, the pattern is:Grundy(n) = 0 if and only if n is a power of 2. For all other n, Grundy > 0 → first player wins. This is because splitting a pile into d equal piles creates a symmetric game that cancels out if d is even, and reduces to Grundy(n/d) if d is odd. Powers of 2 have only even divisors (except 1), which creates a cascading zero result.
ProblemEmbed: stone-division
4. Predict the Winner — Generalization
Problem: Given an integer array nums, two players take turns picking from either end. The player with the higher total score wins. (This is identical to Stone Game I, but with any length — even or odd — and ties possible.) Return true if the first player can force a win (score ≥ opponent).
🤔 Striver-Style Thinking
"Same recurrence as Stone Game I! But this time, n can be even or odd, and we need to check if the first player's score ≥ second player's score. The DP approach is identical: dp[i][j] = max(nums[i] - dp[i+1][j], nums[j] - dp[i][j-1]). Return dp[0][n-1] >= 0."
Note
🔗 Relationship to Stone Game I: Predict the Winner IS Stone Game I without the "even n, odd sum" constraint. It's the more general problem. LeetCode has both (486. Predict the Winner and 877. Stone Game). The solutions are identical!
ProblemEmbed: predict-the-winner
5. Minimax DP — The Universal Template
Every stone game problem follows the same minimax DP pattern. Here's the universal framework:
The Minimax DP Template
Matrix of Stone Game Problems
Problem
State
Action
DP Type
Complexity
Stone Game I
(i, j)
Pick left or right
2D, net advantage
O(n²)
Stone Game II
(i, M)
Take 1..2M from front
2D, max score
O(n²)
Stone Game III
i
Take 1/2/3 from front
1D, suffix DP
O(n)
Stone Game IV
n
Remove perfect square
1D, boolean DP
O(n√n)
Stone Game V
(i, j)
Split at k, keep larger
2D, split DP
O(n³)
Stone Game VI
—
Pick any stone
Greedy (sort)
O(n log n)
Stone Game VII
(i, j)
Pick ends, diff scoring
2D, sum-based
O(n²)
Stone Game VIII
i
Take prefix (not all)
1D, prefix scan
O(n)
Stone Game IX
counts
Remove 1 stone
Modulo counting
O(n)
Stone Division
n
Split into d equal piles
Grundy numbers
O(n√n)
Tip
⚡ Interview Shortcut: When you see a "two players optimal, pick from ends" problem, immediately write the recurrence:dp[i][j] = max(value[i] - dp[i+1][j], value[j] - dp[i][j-1]). This works for 90% of "optimal play" problems. The only variations are WHAT value you get (the picked stone vs sum of remaining) and HOW MANY stones you can take.
🎯 Interview Cheat Sheet
Key Insight
Q1: When do I use minimax DP vs. Nim-sum vs. Grundy? Minimax DP: Game state changes based on player's choice (pick from ends, take X stones). Scoring matters. Nim-sum (XOR): Multiple independent piles, each move affects one pile. Last move wins (no scores). Grundy numbers: Game can be split into independent sub-games with complex rules.
Key Insight
Q2: The dp[i][j] = max(value[i] - dp[i+1][j], value[j] - dp[i][j-1]) recurrence — why subtraction? Because dp[i+1][j] is the OPPONENT's net advantage from that subarray. If I take value[i] now, the opponent will have dp[i+1][j] more than me in the rest. So my total advantage = what I took - opponent's advantage on the rest. If this is positive, I'm ahead.
Key Insight
Q3: What's the difference between Stone Game I and VII? SG I: Your score = value of the stone you take. SG VII: Your score = sum of ALL REMAINING stones after your pick. The recurrence is similar but uses sum(i+1,j) - dp[i+1][j] instead of piles[i] - dp[i+1][j].
Key Insight
Q4: Can I always optimize 2D DP to 1D? For "pick from ends" problems (Stone Game I, VII, Predict Winner), YES — because dp[i][j] only depends on dp[i+1][j] and dp[i][j-1]. For split-based problems (Stone Game V), NO — you need the full 2D table.
Key Insight
Q5: What if the problem adds a constraint like "can only take stones from the front"? Then it reduces to 1D DP (Stone Game III, IV, VIII). The state becomes (i) instead of (i,j) because you never need to track the right boundary — it's always the end.
Key Insight
Q6: How do I recognize if a game is Nim vs. Stone Game? Nim: Multiple piles, remove stones from one pile, last move wins. XOR of pile sizes determines winner. Stone Game: One row of values, pick from ends or splits, scoring matters. DP determines winner. Confusion point: Both have "stone" in the name but are fundamentally different!
Key Insight
Q7: "At each position, I either pick left or right — let's choose max" — when to say this? Say this EXACT phrase when explaining Stone Game I, VII, or Predict the Winner in interviews. It signals you understand the recursive minimax framework. The interviewer will immediately recognize you've seen this pattern before.
📝 Key Takeaways
Unified minimax DP pattern: "At each position, I choose an action. My score = whatITook + (remaining - opponentScoreOnRemaining)"
dp[i][j] = max(value[i] - dp[i+1][j], value[j] - dp[i][j-1]) is the master recurrence for "pick from ends" problems
Subtraction, not addition: dp[i+1][j] is opponent's advantage. We subtract because we want MY net advantage over opponent
Prefix sums turn O(n³) into O(n²) for split-based games (Stone Game V)
From-ends → 2D DP, from-front → 1D DP — the state space shrinks when you can only pick from one side
Stone Game VI is NOT DP — it's a greedy sort by combined value. Recognize when optimal play ≠ minimax recursion
Stone Game IX is modulo counting, not scoring — losing condition is making sum divisible by 3. Completely different framework
Stone Division uses Grundy numbers — splitting into equal piles creates XOR symmetry that simplifies to Grundy(n/d) parity
Space optimization: "Pick from ends" can use 1D array (O(n) space) instead of 2D (O(n²)). Always ask if space matters
Practice! — The Playground below has 10 problems. Start with Stone Game I (minimax foundation), then II (M multiplier), V (split), finally IX (modulo strategy).
Note
🔮 What's Next? You've conquered the Stone Game series! Move to Grundy Numbers & the Sprague–Grundy Theorem to unlock the universal solver for ALL impartial combinatorial games.
// Option 1: Pick from LEFT (piles[i])// - I get piles[i] stones NOW// - Opponent faces subarray piles[i+1..j]// - Opponent's optimal score = dp[i+1][j]// - My net = piles[i] - dp[i+1][j]// Option 2: Pick from RIGHT (piles[j])// - I get piles[j] stones NOW// - Opponent faces subarray piles[i..j-1]// - Opponent's optimal score = dp[i][j-1]// - My net = piles[j] - dp[i][j-1]// I choose the MAXIMUM of these two:// dp[i][j] = max(piles[i] - dp[i+1][j], piles[j] - dp[i][j-1])
function stoneGameI(piles) { const n = piles.length; const dp = Array.from({ length: n }, () => Array(n).fill(0)); // Base case: single stone for (let i = 0; i < n; i++) dp[i][i] = piles[i]; // Fill DP table by subarray length for (let len = 2; len <= n; len++) { for (let i = 0; i <= n - len; i++) { const j = i + len - 1; const pickLeft = piles[i] - dp[i + 1][j]; const pickRight = piles[j] - dp[i][j - 1]; dp[i][j] = Math.max(pickLeft, pickRight); } } // Alice wins if dp[0][n-1] > 0 return dp[0][n - 1] > 0;}// Alternative: Space-optimized O(n) using 1D arrayfunction stoneGameIOptimized(piles) { const n = piles.length; const dp = [...piles]; // dp[i] = dp[i][i] initial state for (let len = 2; len <= n; len++) { for (let i = 0; i <= n - len; i++) { const j = i + len - 1; // dp[i] currently holds dp[i+1][j-1] from previous iteration // We need dp[i+1][j] (current dp[i+1]) and dp[i][j-1] (current dp[i]) const pickLeft = piles[i] - dp[i + 1]; const pickRight = piles[j] - dp[i]; dp[i] = Math.max(pickLeft, pickRight); } } return dp[0] > 0;}
function stoneGameII(piles) { const n = piles.length; const suffix = Array(n + 1).fill(0); for (let i = n - 1; i >= 0; i--) { suffix[i] = suffix[i + 1] + piles[i]; } // dp[i][M] = max stones current player can get from piles[i:] with multiplier M const dp = Array.from({ length: n + 1 }, () => Array(n + 1).fill(0)); function solve(i, M) { if (i >= n) return 0; if (dp[i][M] !== 0) return dp[i][M]; let maxStones = 0; // Try taking X = 1 to 2M stones for (let X = 1; X <= 2 * M && i + X <= n; X++) { const taken = suffix[i] - suffix[i + X]; const opponent = solve(i + X, Math.max(M, X)); const myTotal = taken + (suffix[i + X] - opponent); maxStones = Math.max(maxStones, myTotal); } dp[i][M] = maxStones; return maxStones; } const aliceScore = solve(0, 1); const total = suffix[0]; return aliceScore > total - aliceScore;}// Iterative bottom-up version:function stoneGameIIBottomUp(piles) { const n = piles.length; const suffix = Array(n + 1).fill(0); for (let i = n - 1; i >= 0; i--) suffix[i] = suffix[i + 1] + piles[i]; const dp = Array.from({ length: n + 1 }, () => Array(n + 1).fill(0)); for (let i = n - 1; i >= 0; i--) { for (let M = 1; M <= n; M++) { for (let X = 1; X <= 2 * M && i + X <= n; X++) { const opponent = dp[i + X][Math.max(M, X)]; dp[i][M] = Math.max(dp[i][M], suffix[i] - opponent); } } } return dp[0][1] > suffix[0] - dp[0][1];}
function stoneGameIII(stoneValue) { const n = stoneValue.length; const suffix = Array(n + 1).fill(0); for (let i = n - 1; i >= 0; i--) suffix[i] = suffix[i + 1] + stoneValue[i]; // dp[i] = max score current player can get from piles[i:] const dp = Array(n + 1).fill(0); for (let i = n - 1; i >= 0; i--) { let best = -Infinity; for (let X = 1; X <= 3 && i + X <= n; X++) { // Current player takes piles[i..i+X-1], opponent gets dp[i+X] best = Math.max(best, suffix[i] - dp[i + X]); } dp[i] = best; } const alice = dp[0]; const bob = suffix[0] - alice; if (alice > bob) return "Alice"; if (alice < bob) return "Bob"; return "Tie";}
function stoneGameIV(n) { // Generate all perfect squares up to n const squares = []; for (let i = 1; i * i <= n; i++) squares.push(i * i); const dp = Array(n + 1).fill(false); dp[0] = false; // No stones → current player loses for (let i = 1; i <= n; i++) { for (const s of squares) { if (s > i) break; if (!dp[i - s]) { dp[i] = true; // Found a winning move! break; } } } return dp[n];}// Mathematical shortcut (works for this specific problem):function stoneGameIVMath(n) { // The pattern: Alice wins unless n % (some period) == 0 // But this is NOT guaranteed — always implement DP first! return stoneGameIV(n); // Stick with DP for safety}
function stoneGameV(stoneValue) { const n = stoneValue.length; const prefix = Array(n + 1).fill(0); for (let i = 0; i < n; i++) prefix[i + 1] = prefix[i] + stoneValue[i]; const dp = Array.from({ length: n }, () => Array(n).fill(0)); // sum[i..j] using prefix function sum(i, j) { return prefix[j + 1] - prefix[i]; } // Process by increasing subarray length for (let len = 2; len <= n; len++) { for (let i = 0; i <= n - len; i++) { const j = i + len - 1; let best = 0; for (let k = i; k < j; k++) { const left = sum(i, k); const right = sum(k + 1, j); if (left < right) { // Keep left (smaller), opponent plays right best = Math.max(best, left + dp[i][k]); } else if (left > right) { // Keep right (smaller), opponent plays left best = Math.max(best, right + dp[k + 1][j]); } else { // Equal — choose either best = Math.max(best, left + dp[i][k], right + dp[k + 1][j]); } } dp[i][j] = best; } } return dp[0][n - 1];}
function stoneGameVI(aliceValues, bobValues) { const n = aliceValues.length; // Create array of [combinedValue, aliceValue, bobValue, index] const stones = []; for (let i = 0; i < n; i++) { stones.push({ combined: aliceValues[i] + bobValues[i], alice: aliceValues[i], bob: bobValues[i], }); } // Sort by combined value DESCENDING stones.sort((a, b) => b.combined - a.combined); let aliceScore = 0; let bobScore = 0; for (let i = 0; i < n; i++) { if (i % 2 === 0) { aliceScore += stones[i].alice; // Alice' turn } else { bobScore += stones[i].bob; // Bob's turn } } if (aliceScore > bobScore) return 1; if (aliceScore < bobScore) return -1; return 0;}// Why sorting by combined value works:// When Alice picks stone i, the net gain over Bob is:// aliceValues[i] - (what Bob would have gotten if Alice didn't pick it)// If Alice doesn't pick stone i, Bob might pick it and get bobValues[i].// So Alice's pick prevents Bob from getting bobValues[i].// Net advantage = aliceValues[i] - (-bobValues[i]) = aliceValues[i] + bobValues[i]// Alice wants to MAXIMIZE this advantage → pick highest combined value!
function stoneGameVII(stones) { const n = stones.length; const prefix = Array(n + 1).fill(0); for (let i = 0; i < n; i++) prefix[i + 1] = prefix[i] + stones[i]; function sum(i, j) { return prefix[j + 1] - prefix[i]; } const dp = Array.from({ length: n }, () => Array(n).fill(0)); for (let len = 2; len <= n; len++) { for (let i = 0; i <= n - len; i++) { const j = i + len - 1; // Pick left: get sum[i+1..j], then opponent advantage = dp[i+1][j] const pickLeft = sum(i + 1, j) - dp[i + 1][j]; // Pick right: get sum[i..j-1], then opponent advantage = dp[i][j-1] const pickRight = sum(i, j - 1) - dp[i][j - 1]; dp[i][j] = Math.max(pickLeft, pickRight); } } return dp[0][n - 1]; // Alice's max advantage over Bob}
function stoneGameVIII(stones) { const n = stones.length; const prefix = Array(n + 1).fill(0); for (let i = 0; i < n; i++) prefix[i + 1] = prefix[i] + stones[i]; // dp[i] = max advantage current player can get from suffix starting at i const dp = Array(n + 1).fill(0); let maxDiff = prefix[n] - dp[n]; // Best difference seen so far // Process from right to left for (let i = n - 1; i >= 0; i--) { // If we take prefix up to i, opponent gets dp[i+1] from remaining // Our advantage = sum[0..i] - dp[i+1] dp[i] = maxDiff; // Update maxDiff for next iteration // At position i, taking prefix [0..i] gives sum[0..i] - dp[i+1] maxDiff = Math.max(maxDiff, prefix[i + 1] - dp[i + 1]); } return dp[0];}// Simplified:function stoneGameVIII(stones) { const n = stones.length; let suffixSum = stones.reduce((a, b) => a + b, 0); let best = suffixSum; // Take all stones on last possible turn for (let i = n - 1; i > 0; i--) { suffixSum -= stones[i]; // Current player takes prefix [0..i-1], opponent gets dp from position i // dp[i] = max score from position i onward best = Math.max(best, suffixSum - best); } return best;}
function stoneGameIX(stones) { // Count stones by their value modulo 3 let count0 = 0, count1 = 0, count2 = 0; for (const s of stones) { if (s % 3 === 0) count0++; else if (s % 3 === 1) count1++; else count2++; } // If count1 and count2 are both 0, all stones are divisible by 3 // Alice MUST pick one → sum % 3 = 0 → Alice LOSES immediately if (count1 === 0 && count2 === 0) return false; // If count0 is even: // Alice wins if min(count1, count2) > 0 // If count0 is odd: // Alice wins if abs(count1 - count2) > 2 if (count0 % 2 === 0) { return Math.min(count1, count2) > 0; } else { return Math.abs(count1 - count2) > 2; }}// Detailed logic explanation:// The game is about using 1s and 2s to AVOID making the sum % 3 = 0// Stones with value % 3 == 0 just change turn order (neutral)// If count0 is even, 0s don't affect turn parity// If count0 is odd, 0s flip turn parity// To avoid losing: Alice needs to ensure she never creates sum % 3 == 0// while forcing Bob into that situation
function stoneDivision(n) { const grundy = Array(n + 1).fill(0); for (let i = 2; i <= n; i++) { const reachable = new Set(); // Find all divisors d of i where d > 0 and d < i for (let d = 1; d * d <= i; d++) { if (i % d === 0) { const d1 = d; const d2 = i / d; // For divisor d1: split into d1 piles of size i/d1 if (d1 < i) { const g = grundy[i / d1]; const xorVal = (d1 % 2 === 0) ? 0 : g; reachable.add(xorVal); } // For divisor d2 (different from d1) if (d2 !== d1 && d2 < i) { const g = grundy[i / d2]; const xorVal = (d2 % 2 === 0) ? 0 : g; reachable.add(xorVal); } } } // mex — find smallest non-negative integer not in reachable let g = 0; while (reachable.has(g)) g++; grundy[i] = g; } return grundy[n] !== 0;}// Pattern detection for large n (up to 10^12):// Grundy(n) = 0 only for n = 1 or n = 2// For n >= 3, Grundy(n) > 0 → First player wins// But verify with DP for small n, then generalize
function predictTheWinner(nums) { const n = nums.length; const dp = Array.from({ length: n }, () => Array(n).fill(0)); for (let i = 0; i < n; i++) dp[i][i] = nums[i]; for (let len = 2; len <= n; len++) { for (let i = 0; i <= n - len; i++) { const j = i + len - 1; dp[i][j] = Math.max(nums[i] - dp[i+1][j], nums[j] - dp[i][j-1]); } } return dp[0][n - 1] >= 0;}// Space-optimized versionfunction predictTheWinnerOpt(nums) { const n = nums.length; const dp = [...nums]; // dp[i] initially = dp[i][i] for (let len = 2; len <= n; len++) { for (let i = 0; i <= n - len; i++) { const j = i + len - 1; dp[i] = Math.max(nums[i] - dp[i + 1], nums[j] - dp[i]); } } return dp[0] >= 0;}
// Universal Minimax DP Template for Two-Player Gamesfunction solve(values) { const n = values.length; // Step 1: Precompute prefix/suffix sums if needed const prefix = Array(n + 1).fill(0); for (let i = 0; i < n; i++) prefix[i + 1] = prefix[i] + values[i]; function sum(i, j) { return prefix[j + 1] - prefix[i]; } // Step 2: Initialize DP // dp[i][j] = maximum NET advantage for current player on subarray i..j const dp = Array.from({ length: n }, () => Array(n).fill(0)); // Step 3: Base case — single element for (let i = 0; i < n; i++) dp[i][i] = values[i]; // Step 4: Fill DP — increase subarray length for (let len = 2; len <= n; len++) { for (let i = 0; i <= n - len; i++) { const j = i + len - 1; // For each possible move at this state // "At this position, I CHOOSE to pick left, pick right, or some other action" // dp[i][j] = max over all valid moves of (myGain - opponentAdvantage) // Example: pick from ends (Stone Game I, VII) const pickLeft = values[i] - dp[i + 1][j]; // or sum(i+1,j) - dp[i+1][j] for SG VII const pickRight = values[j] - dp[i][j - 1]; // or sum(i,j-1) - dp[i][j-1] for SG VII dp[i][j] = Math.max(pickLeft, pickRight); } } // Step 5: Answer return dp[0][n - 1]; // Positive → first player wins}