class Solution:
def stoneGameVIII(self, stones):
n = len(stones)
# Compute prefix sums
for i in range(1, n):
stones[i] += stones[i-1]
# dp[i]: max score Alice can get if there are i stones left
# We only need to keep track of dp[i+1], so do it backwards
best = stones[-1]
for i in range(n-2, 0, -1):
best = max(best, stones[i] - best)
return best
class Solution {
public:
int stoneGameVIII(vector<int>& stones) {
int n = stones.size();
for (int i = 1; i < n; ++i)
stones[i] += stones[i-1];
int best = stones[n-1];
for (int i = n-2; i > 0; --i) {
best = max(best, stones[i] - best);
}
return best;
}
};
class Solution {
public int stoneGameVIII(int[] stones) {
int n = stones.length;
for (int i = 1; i < n; ++i) {
stones[i] += stones[i-1];
}
int best = stones[n-1];
for (int i = n-2; i > 0; --i) {
best = Math.max(best, stones[i] - best);
}
return best;
}
}
var stoneGameVIII = function(stones) {
let n = stones.length;
for (let i = 1; i < n; ++i) {
stones[i] += stones[i-1];
}
let best = stones[n-1];
for (let i = n-2; i > 0; --i) {
best = Math.max(best, stones[i] - best);
}
return best;
};
In the Stone Game VIII problem, you are given an integer array stones
representing a row of stones. Alice and Bob play a game in turns, with Alice always going first.
Your task is to return the maximum score Alice can achieve over Bob, given the initial stones
array.
Key constraints:
2 <= stones.length <= 10^5
-10^4 <= stones[i] <= 10^4
At first glance, this problem resembles other classic two-player games where players make optimal choices. The naive approach would be to simulate every possible move sequence, but with up to 105 stones, this is computationally infeasible.
The key insight is to realize that after the first move (removing at least two stones), the leftmost stone is always a prefix sum of the array. Each subsequent move also replaces the leftmost stones with their prefix sum.
Instead of tracking the entire array, we can focus on the prefix sums and use dynamic programming to keep track of the optimal difference Alice can achieve at each stage. The problem then becomes finding, for each possible split, the best score difference for Alice, assuming both play optimally.
The transition from brute-force to optimization comes from recognizing overlapping subproblems and that only the prefix sums matter, not the full array at each step.
Let’s break down the efficient solution step by step:
stones
. After each move, the new leftmost stone is always a prefix sum.
dp[i]
as the maximum score difference Alice can achieve if there are i
stones left (i.e., after making n-i
moves).
dp[i] = max(prefix_sum[i] - dp[i+1], dp[i+1])
This approach ensures we only make a single pass over the array, yielding an efficient solution.
Consider the input stones = [-1, 2, -3, 4, -5]
.
best = -3
(prefix_sum[4])best = max(2 - (-3), -3) = max(5, -3) = 5
best = max(-2 - 5, 5) = max(-7, 5) = 5
best = max(1 - 5, 5) = max(-4, 5) = 5
This shows how, at each step, we consider the best possible outcome for Alice, assuming both play optimally.
Brute-force approach: If we tried all possible sequences, the time complexity would be exponential, as each move can split the array in different ways.
Optimized approach:
stones
. We compute prefix sums in one pass and process the DP in another pass.The Stone Game VIII problem is a classic example of reducing a two-player game to a dynamic programming problem by focusing on prefix sums and optimal substructure. The key insight is that after each move, the game state can be represented by a prefix sum, allowing us to use a simple recurrence to compute the best possible outcome for Alice. The final algorithm is both elegant and efficient, running in linear time with minimal extra space.