Alice and Bob continue their games with piles of stones. There are a 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.
Alice and Bob take turns, with Alice starting first.
On each player's turn, that player can take all the stones in the first X remaining piles, where 1 <= X <= 2M. Then, we set M = max(M, X). Initially, M = 1.
The game continues until all the stones have been taken.
Assuming Alice and Bob play optimally, return the maximum number of stones Alice can get.
Example 1:
Input: piles = [3,1,2,5,7]
Output: 10Explanation: Alice takes first pile, then Bob takes the second pile, then Alice takes the next two piles and Bob takes the last remaining pile. This makes Alice's score 3 + 2 + 5 = 10, which is the maximum Alice can get.
Example 2:
Input: piles = [4,3,2,5,10]
Output: 11Constraints:
1 <= piles.length <= 1001 <= piles[i] <= 10,000Before attempting this problem, you should be comfortable with:
This is a two-player game where Alice and Bob take turns picking piles from the front. The key insight is that both players play optimally, meaning Alice tries to maximize her score while Bob tries to minimize Alice's score. We can model this using recursion with memoization, tracking whose turn it is, the current index, and the value of M. When it's Alice's turn, she picks the option that maximizes her total; when it's Bob's turn, he picks the option that minimizes Alice's total.
dfs(alice, i, M) where alice indicates whose turn it is, i is the current pile index, and M is the maximum number of piles that can be taken.i == n, return 0 (no more piles).X piles (1 to 2M) and maximize the result by adding the stones taken plus the recursive call for Bob's turn.X piles and minimize the result since Bob wants to minimize Alice's score.M to max(M, X) after each move.[2][n][n+1].dfs(True, 0, 1) to get Alice's maximum score.class Solution:
def stoneGameII(self, piles: List[int]) -> int:
dp = {}
def dfs(alice, i, M):
if i == len(piles):
return 0
if (alice, i, M) in dp:
return dp[(alice, i, M)]
res = 0 if alice else float("inf")
total = 0
for X in range(1, 2 * M + 1):
if i + X > len(piles):
break
total += piles[i + X - 1]
if alice:
res = max(res, total + dfs(not alice, i + X, max(M, X)))
else:
res = min(res, dfs(not alice, i + X, max(M, X)))
dp[(alice, i, M)] = res
return res
return dfs(True, 0, 1)Instead of tracking both players separately, we can simplify by focusing on a single player's perspective. At each position, the current player wants to maximize their own score. The trick is that whatever stones remain after the current player's turn will be split optimally by the opponent. Using suffix sums, if the current player takes some stones, their score equals those stones plus whatever remains minus the opponent's optimal result from the remaining position.
suffix_sum[i] is the total stones from index i to the end.dfs(i, M) to return the maximum stones the current player can get starting at index i with parameter M.i == n, return 0.X from 1 to 2M, the current player can take suffix_sum[i] - dfs(i + X, max(M, X)). This works because the current player gets all remaining stones minus what the opponent will optimally take.[n][n+1].dfs(0, 1) to get Alice's maximum score.class Solution:
def stoneGameII(self, piles: List[int]) -> int:
n = len(piles)
dp = [[None] * (n + 1) for _ in range(n)]
suffix_sum = [0] * n
suffix_sum[-1] = piles[-1]
for i in range(n - 2, -1, -1):
suffix_sum[i] = piles[i] + suffix_sum[i + 1]
def dfs(i, M):
if i == n:
return 0
if dp[i][M] is not None:
return dp[i][M]
res = 0
for X in range(1, 2 * M + 1):
if i + X > n:
break
res = max(res, suffix_sum[i] - dfs(i + X, max(M, X)))
dp[i][M] = res
return res
return dfs(0, 1)We can convert the top-down approach to bottom-up by filling the DP table from the end of the piles array backward. At each position, we compute the optimal result for both Alice and Bob, considering all possible moves. Working backward ensures that when we compute a state, all states it depends on have already been computed.
dp[2][n+1][n+1] where the first dimension represents the player (1 for Alice, 0 for Bob).i = n-1 down to 0, and for each M from 1 to n.dp[1][i][M] = 0 and try taking X piles (1 to 2M), computing total + dp[0][i+X][max(M, X)] and taking the maximum.dp[0][i][M] = infinity and try taking X piles, computing dp[1][i+X][max(M, X)] and taking the minimum.dp[1][0][1] for Alice's maximum score starting at position 0 with M=1.class Solution:
def stoneGameII(self, piles: List[int]) -> int:
n = len(piles)
dp = [[[0] * (n + 1) for _ in range(n + 1)] for _ in range(2)]
for i in range(n - 1, -1, -1):
for M in range(1, n + 1):
total = 0
dp[1][i][M] = 0
dp[0][i][M] = float("inf")
for X in range(1, 2 * M + 1):
if i + X > n:
break
total += piles[i + X - 1]
dp[1][i][M] = max(dp[1][i][M], total + dp[0][i + X][max(M, X)])
dp[0][i][M] = min(dp[0][i][M], dp[1][i + X][max(M, X)])
return dp[1][0][1]Combining the bottom-up approach with the suffix sum optimization gives us a cleaner solution. Since we treat both players symmetrically (each maximizes their own score), we only need a 2D DP table. The suffix sum lets us compute how many stones the current player gets by subtracting the opponent's optimal result from the remaining total.
suffix_sum[i] is the total stones from index i to the end.dp[n+1][n+1] initialized to 0.i = n-1 down to 0, and for each M from 1 to n.X from 1 to 2M (while i + X <= n), compute dp[i][M] = max(dp[i][M], suffix_sum[i] - dp[i+X][max(M, X)]).dp[0][1] as Alice's maximum score.class Solution:
def stoneGameII(self, piles: List[int]) -> int:
n = len(piles)
suffix_sum = [0] * n
suffix_sum[-1] = piles[-1]
for i in range(n - 2, -1, -1):
suffix_sum[i] = piles[i] + suffix_sum[i + 1]
dp = [[0] * (n + 1) for _ in range(n + 1)]
for i in range(n - 1, -1, -1):
for M in range(1, n + 1):
for X in range(1, 2 * M + 1):
if i + X > n:
break
dp[i][M] = max(dp[i][M], suffix_sum[i] - dp[i + X][max(M, X)])
return dp[0][1]A frequent error is updating M incorrectly after a move. The rule is M = max(M, X) where X is the number of piles taken, not M = X or M = M + X. Failing to take the maximum means M might decrease, which violates the game rules and leads to wrong answers.
Since both players play optimally but only Alice's score matters for the answer, it is easy to mix up the logic. When using the minimax approach, Alice maximizes her score while Bob minimizes it. When using the suffix sum trick, both players maximize their own score, and the recursion naturally handles the alternation.
The player can take X piles where 1 <= X <= 2*M. A common mistake is iterating X from 0 to 2*M or from 1 to 2*M - 1. Ensure your loop correctly covers exactly the valid range of moves, and check that i + X does not exceed the array length before accessing elements.