Stone Game VII
MedPrompt
Alice and Bob take turns playing a game, with Alice starting first.
There are n stones arranged in a row. On each player's turn, they can remove either the leftmost stone or the rightmost stone from the row and receive points equal to the sum of the remaining stones' values in the row. The winner is the one with the higher score when there are no stones left to remove.
Bob found that he will always lose this game (poor Bob, he always loses), so he decided to minimize the score's difference. Alice's goal is to maximize the difference in the score.
Given an array of integers stones where stones[i] represents the value of the ith stone from the left, return the difference in Alice and Bob's score if they both play optimally.
Example 1:
Input: stones = [5,3,1,4,2]
Output: 6
Explanation:
- Alice removes 2 and gets 5 + 3 + 1 + 4 = 13 points. Alice = 13, Bob = 0, stones = [5,3,1,4].
- Bob removes 5 and gets 3 + 1 + 4 = 8 points. Alice = 13, Bob = 8, stones = [3,1,4].
- Alice removes 3 and gets 1 + 4 = 5 points. Alice = 18, Bob = 8, stones = [1,4].
- Bob removes 1 and gets 4 points. Alice = 18, Bob = 12, stones = [4].
- Alice removes 4 and gets 0 points. Alice = 18, Bob = 12, stones = [].
The score difference is 18 - 12 = 6.Example 2:
Input: stones = [7,90,5,1,100,10,10,2]
Output: 122
Constraints:
n == stones.length2 <= n <= 10001 <= stones[i] <= 1000
Approaches
4 approaches with complexity analysis and trade-offs.
This approach optimizes the space complexity of the bottom-up DP solution. By observing the state transitions, we can see that to compute the values for a subarray of a certain length, we only need the results for subarrays of the immediately smaller length. This means to compute the current row i of our DP table, we only need the next row i+1. This dependency allows us to reduce the space from O(n^2) to O(n).
Algorithm
- Create a prefix sum array for
O(1)sum calculations. - Create a 1D DP array
dp[n], initialized to 0. - Iterate the start index
ifromn-2down to0. - In the inner loop, iterate the end index
jfromi+1up ton-1. - Inside the inner loop, calculate the two possible differences:
a.
diff1 = (prefixSum[j+1] - prefixSum[i+1]) - dp[j]. Heredp[j]holds the value from the previous outer loop, which corresponds todp[i+1][j]. b.diff2 = (prefixSum[j] - prefixSum[i]) - dp[j-1]. Heredp[j-1]holds the value just computed in the current inner loop, which corresponds todp[i][j-1]. - Update
dp[j]withmax(diff1, diff2). - After the loops,
dp[n-1]will contain the result for the entire arraystones[0...n-1].
Walkthrough
The recurrence is dp[i][j] = max(sum(i+1, j) - dp[i+1][j], sum(i, j-1) - dp[i][j-1]). Let's iterate by the starting index i from n-2 down to 0, and the ending index j from i+1 up to n-1. When we compute dp[i][j], we need dp[i+1][j] (from the 'next' row i+1) and dp[i][j-1] (from the 'current' row i, but a previous column). This structure allows for space optimization. We can use a 1D array, say dp[n]. Let dp[j] represent the value for the current row i and column j. When we are calculating the row for i, the dp array will hold the values from the previous iteration, which corresponds to row i+1. The recurrence becomes: dp[j] = max(sum(i+1, j) - dp[j], sum(i, j-1) - dp[j-1]), where we update the dp array in place.
class Solution { public int stoneGameVII(int[] stones) { int n = stones.length; int[] prefixSum = new int[n + 1]; for (int i = 0; i < n; i++) { prefixSum[i + 1] = prefixSum[i] + stones[i]; } int[] dp = new int[n]; // i is the starting index of the subarray for (int i = n - 2; i >= 0; i--) { // j is the ending index of the subarray for (int j = i + 1; j < n; j++) { // Sum of stones from i+1 to j int sumTakeLeft = prefixSum[j + 1] - prefixSum[i + 1]; // Sum of stones from i to j-1 int sumTakeRight = prefixSum[j] - prefixSum[i]; // dp[j] currently holds dp[i+1][j] from the previous outer loop iteration // dp[j-1] holds dp[i][j-1] from the current inner loop iteration int diff1 = sumTakeLeft - dp[j]; int diff2 = sumTakeRight - dp[j - 1]; dp[j] = Math.max(diff1, diff2); } } return dp[n - 1]; }}Complexity
Time
O(n^2). The nested loops run O(n^2) times, and each step is O(1).
Space
O(n). We use a 1D array `dp` of size `n` and a prefix sum array of size `n+1`.
Trade-offs
Pros
Most efficient in terms of space complexity.
Maintains the optimal O(n^2) time complexity.
Cons
The logic for in-place updates and loop directions can be tricky to reason about compared to the O(n^2) space solution.
Solutions
Solution
class Solution {private int[] s;private Integer[][] f;public int stoneGameVII(int[] stones) { int n = stones.length; s = new int[n + 1]; f = new Integer[n][n]; for (int i = 0; i < n; ++i) { s[i + 1] = s[i] + stones[i]; } return dfs(0, n - 1); }private int dfs(int i, int j) { if (i > j) { return 0; } if (f[i][j] != null) { return f[i][j]; } int a = s[j + 1] - s[i + 1] - dfs(i + 1, j); int b = s[j] - s[i] - dfs(i, j - 1); return f[i][j] = Math.max(a, b); }}Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.