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:
piles = [2,7,9,4,4]
Output: 10
Example 2:
piles = [1,2,3,4,5,100]
Output: 104
How would you approach this problem and what code would you write?
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// Naive recursive solution (for understanding, not efficient enough for larger inputs)
int stoneGameII_recursive(const vector<int>& piles, int i, int M, vector<int>& prefixSum)
{
if (i >= piles.size()) {
return 0;
}
int remaining = piles.size() - i;
int maxStones = 0;
for (int X = 1; X <= 2 * M && X <= remaining; ++X) {
int currentStones = prefixSum[i + X] - prefixSum[i];
// The other player's score is total - our score, we want to maximize our score so minimize the score of the other player
int nextM = max(M, X);
int opponentStones = stoneGameII_recursive(piles, i + X, nextM, prefixSum);
maxStones = max(maxStones, currentStones - opponentStones);
}
return maxStones;
}
// Optimized dynamic programming solution
int stoneGameII_dp(const vector<int>& piles)
{
int n = piles.size();
vector<int> prefixSum(n + 1, 0);
for (int i = 0; i < n; ++i) {
prefixSum[i + 1] = prefixSum[i] + piles[i];
}
// dp[i][M] represents the maximum difference between Alice and Bob's score
// starting from index i with current M
vector<vector<int>> dp(n, vector<int>(n, 0));
// Iterate backwards to fill dp table (bottom-up)
for (int i = n - 1; i >= 0; --i) {
for (int M = n; M >= 1; --M) {
if (i + 2 * M >= n) {
// Can take all remaining stones
dp[i][M] = prefixSum[n] - prefixSum[i];
} else {
// Iterate through possible values of X
for (int X = 1; X <= 2 * M; ++X) {
if (i + X <= n) {
int nextM = max(M, X);
dp[i][M] = max(dp[i][M], (prefixSum[i + X] - prefixSum[i]) - dp[i + X][nextM]);
}
}
}
}
}
// The total stones are sum of the array,
// so Alice score = (total stones + difference) / 2
return (prefixSum[n] + dp[0][1]) / 2;
}
int main()
{
vector<int> piles1 = {2, 7, 9, 4, 4};
cout << "Maximum stones Alice can get (DP): " << stoneGameII_dp(piles1) << endl;
vector<int> piles2 = {1, 2, 3, 4, 5, 100};
cout << "Maximum stones Alice can get (DP): " << stoneGameII_dp(piles2) << endl;
vector<int> piles3 = {2, 7, 9, 4, 4};
vector<int> prefixSum3(piles3.size() + 1, 0);
for (int i = 0; i < piles3.size(); ++i) {
prefixSum3[i + 1] = prefixSum3[i] + piles3[i];
}
cout << "Maximum stones Alice can get (Recursive): " << stoneGameII_recursive(piles3, 0, 1, prefixSum3) << endl;
return 0;
}
i
is out of bounds (i.e., i >= piles.size()
), return 0 because there are no more stones to take.X
(from 1 to 2*M
), ensuring X
does not exceed the remaining piles.currentStones = prefixSum[i + X] - prefixSum[i]
. The prefix sum helps to get sum of stones in O(1).stoneGameII_recursive
for the remaining piles.maxStones
to maximize Alice's score: maxStones = max(maxStones, currentStones - opponentStones)
.prefixSum
array to efficiently compute sum of stones.dp[n][n]
table with zeros. dp[i][M]
represents the maximum difference Alice can achieve starting from index i
with current M
.i = n - 1
to 0
).M
(from n
down to 1
).i + 2 * M >= n
, Alice can take all remaining stones, so dp[i][M] = prefixSum[n] - prefixSum[i]
.X
(from 1
to 2 * M
):
dp[i][M]
with the maximum difference Alice can get: dp[i][M] = max(dp[i][M], (prefixSum[i + X] - prefixSum[i]) - dp[i + X][nextM])
, where nextM = max(M, X)
.(prefixSum[n] + dp[0][1]) / 2
.n-1
to 0
and from n
to 1
. The inner loop iterates up to 2*M
which is at most 2*N
. Thus the overall complexity is O(N * N * N).dp[n][n]
table. Also, O(N) for the prefix sum array. Thus the overall space is O(N^2 + N) -> O(N^2)n x n
.piles
is empty, return 0. Add a check at the beginning of the function to handle this case.piles[i]
are zero, the maximum stones Alice can get is zero.M
is constrained by the number of remaining piles.