877. Stone Game - Explanation
Description
Alice and Bob are playing a game with piles of stones. There are an even number of piles arranged in a row, and each pile has a positive integer number of stones piles[i].
The objective of the game is to end with the most stones. The total number of stones across all the piles is odd, so there are no ties.
Alice and Bob take turns, with Alice starting first. Each turn, a player takes the entire pile of stones either from the beginning or from the end of the row. This continues until there are no more piles left, at which point the person with the most stones wins.
Assuming Alice and Bob play optimally, return true if Alice wins the game, or false if Bob wins.
Example 1:
Input: piles = [1,2,3,1]
Output: trueExplanation: Alice takes first pile, then Bob takes the last pile, then Alice takes the pile from the end and Bob takes the last remaining pile. Alice's score is 1 + 3 = 4. Bob's score is 1 + 2 = 3.
Example 2:
Input: piles = [2,1]
Output: trueConstraints:
2 <= piles.length <= 500piles.lengthis even.1 <= piles[i] <= 500sum(piles[i])is odd.
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- Dynamic Programming (Interval DP) - Computing optimal results over ranges [l, r] with subproblems based on shrinking intervals
- Game Theory / Two-Player Games - Understanding how to model turn-based games where both players play optimally
- Recursion with Memoization - Converting recursive solutions to efficient DP by caching overlapping subproblems
- Mathematical Reasoning - Recognizing that structural properties (even pile count, odd total) can lead to direct solutions
1. Recursion
Intuition
This is a two-player game where Alice and Bob take turns picking from either end of the piles array. Both play optimally, meaning each player maximizes their own score. We can simulate this using recursion where we track the current range [l, r] and determine whose turn it is based on the range length. Alice wants to maximize her score, while Bob's moves affect what Alice can collect later.
Algorithm
- Define a recursive function
dfs(l, r)that returns Alice's maximum score for the subarray[l, r]. - If
l > r, return0(no piles left). - Determine if it is Alice's turn: it is Alice's turn when the number of remaining piles
(r - l + 1)is even. - If it is Alice's turn, she can take from either end and we add her choice to the score. Recurse and take the maximum of taking left or right.
- If it is Bob's turn, he takes optimally but we only track Alice's score (add
0for his pick). - Compare Alice's final score against Bob's (total minus Alice's score) to determine the winner.
class Solution:
def stoneGame(self, piles: List[int]) -> bool:
def dfs(l, r):
if l > r:
return 0
even = (r - l) % 2 == 0
left = piles[l] if even else 0
right = piles[r] if even else 0
return max(dfs(l + 1, r) + left, dfs(l, r - 1) + right)
total = sum(piles)
alice_score = dfs(0, len(piles) - 1)
return alice_score > total - alice_scoreTime & Space Complexity
- Time complexity:
- Space complexity:
2. Dynamic Programming (Top-Down)
Intuition
The recursive solution has overlapping subproblems since the same (l, r) range can be reached through different sequences of moves. By memoizing results for each (l, r) pair, we avoid recomputing the same states and achieve polynomial time complexity.
Algorithm
- Create a 2D memoization table
dpinitialized to-1(or use a hash map). - In the recursive function, first check if
dp[l][r]has been computed. If so, return the cached value. - Compute the result as in the plain recursion approach.
- Store the result in
dp[l][r]before returning. - The final answer is whether
dp[0][n-1](Alice's score) is greater thantotal - dp[0][n-1](Bob's score).
class Solution:
def stoneGame(self, piles: List[int]) -> bool:
dp = {}
def dfs(l, r):
if l > r:
return 0
if (l, r) in dp:
return dp[(l, r)]
even = (r - l) % 2 == 0
left = piles[l] if even else 0
right = piles[r] if even else 0
dp[(l, r)] = max(dfs(l + 1, r) + left, dfs(l, r - 1) + right)
return dp[(l, r)]
total = sum(piles)
alice_score = dfs(0, len(piles) - 1)
return alice_score > total - alice_scoreTime & Space Complexity
- Time complexity:
- Space complexity:
3. Dynamic Programming (Bottom-Up)
Intuition
Instead of recursion with memoization, we can fill the DP table iteratively. We process subproblems in order of increasing range length. For each range [l, r], we compute Alice's optimal score based on already-solved smaller ranges [l+1, r] and [l, r-1].
Algorithm
- Create a 2D DP table of size
n x n. - Iterate
lfromn-1down to0(to ensure smaller ranges are solved first). - For each
l, iteraterfromlton-1. - Determine whose turn it is based on
(r - l) % 2. - For base case
l == r, Alice takes the pile if it is her turn, otherwise0. - For larger ranges, take the maximum of choosing left or right (adding the pile value only on Alice's turn).
- Return whether
dp[0][n-1] > total - dp[0][n-1].
class Solution:
def stoneGame(self, piles: List[int]) -> bool:
n = len(piles)
dp = [[0] * n for _ in range(n)]
for l in range(n - 1, -1, -1):
for r in range(l, n):
even = (r - l) % 2 == 0
left = piles[l] if even else 0
right = piles[r] if even else 0
if l == r:
dp[l][r] = left
else:
dp[l][r] = max(dp[l + 1][r] + left, dp[l][r - 1] + right)
total = sum(piles)
alice_score = dp[0][n - 1]
return alice_score > total - alice_scoreTime & Space Complexity
- Time complexity:
- Space complexity:
4. Dynamic Programming (Space Optimized)
Intuition
Looking at the DP transitions, dp[l][r] only depends on dp[l+1][r] and dp[l][r-1]. When iterating by increasing l from right to left and r from left to right, we only need values from the current row being built. This allows us to compress the 2D table into a 1D array.
Algorithm
- Create a 1D DP array of size
n. - Iterate
lfromn-1down to0. - For each
l, iteraterfromlton-1. dp[r]represents the previous value fromdp[l+1][r], anddp[r-1]representsdp[l][r-1].- Update
dp[r]with the maximum score for the current range. - Return whether
dp[n-1] > total - dp[n-1].
class Solution:
def stoneGame(self, piles: List[int]) -> bool:
n = len(piles)
dp = [0] * n
for l in reversed(range(n)):
for r in range(l, n):
even = ((r - l) % 2 == 0)
left = piles[l] if even else 0
right = piles[r] if even else 0
if l == r:
dp[r] = left
else:
dp[r] = max(dp[r] + left, dp[r - 1] + right)
total = sum(piles)
alice_score = dp[n - 1]
return alice_score > (total - alice_score)Time & Space Complexity
- Time complexity:
- Space complexity:
5. Return TRUE
Intuition
Here is the key insight: with an even number of piles and an odd total sum, Alice can always win. She can always choose to take all even-indexed piles or all odd-indexed piles. Since the total is odd, one of these sets must have a larger sum. Alice, moving first, can force the game to give her whichever set she prefers, guaranteeing a win.
Algorithm
- Simply return
true.
Time & Space Complexity
- Time complexity:
- Space complexity:
Common Pitfalls
Overcomplicating the Solution
The mathematical insight that Alice always wins (due to even pile count and odd total) means you can simply return true. However, many solvers implement full DP without recognizing this property. While the DP approach is valid and educational, understanding why Alice always wins demonstrates deeper problem analysis.
Confusing Whose Turn It Is
In the DP approach, determining whose turn it is based on the range [l, r] can be tricky. Alice moves when the number of remaining piles is even (since the game starts with an even number and she goes first). Using (r - l + 1) % 2 == 0 or equivalently (r - l) % 2 == 1 indicates Alice's turn, but off-by-one errors here are common.
Wrong Comparison for Final Result
The question asks whether Alice wins (strictly greater score), not whether she ties or wins. Comparing alice_score >= total - alice_score instead of alice_score > total - alice_score gives incorrect results. Since the total is odd and all values are positive integers, a tie is impossible in this specific problem, but the comparison should still be strictly greater.
Sign in to join the discussion