Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions Hard/1872.Stone-Game-VIII/description.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
# 1872. Stone Game VIII

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, while the number of
stones is **more than one**, they will do the following:

1. Choose an integer `x > 1`, and **remove** the leftmost `x` stones from the row.
2. Add the **sum** of the **removed** stones' values to the player's score.
3. Place a **new stone**, whose value is equal to that sum, on the left side of the row.

The game stops when **only one** stone is left in the row.

The **score difference** between Alice and Bob is `(Alice's score - Bob's score)`.
Alice's goal is to **maximize** the score difference, and Bob's goal is to
**minimize** the score difference.

Given an integer array `stones` of length `n` where `stones[i]` represents the
value of the `i`th stone **from the left**, return the **score difference** between
Alice and Bob if they both play **optimally**.

## Example 1

```text
Input: stones = [-1,2,-3,4,-5]
Output: 5
Explanation:
- Alice removes the first 4 stones, adds (-1) + 2 + (-3) + 4 = 2 to her score, and places a stone of
value 2 on the left. stones = [2,-5].
- Bob removes the first 2 stones, adds 2 + (-5) = -3 to his score, and places a stone of value -3 on
the left. stones = [-3].
The difference between their scores is 2 - (-3) = 5.
```

## Example 2

```text
Input: stones = [7,-6,5,10,5,-2,-6]
Output: 13
Explanation:
- Alice removes all stones, adds 7 + (-6) + 5 + 10 + 5 + (-2) + (-6) = 13 to her score, and places a
stone of value 13 on the left. stones = [13].
The difference between their scores is 13 - 0 = 13.
```

## Example 3

```text
Input: stones = [-10,-12]
Output: -22
Explanation:
- Alice can only make one move, which is to remove both stones. She adds (-10) + (-12) = -22 to her
score and places a stone of value -22 on the left. stones = [-22].
The difference between their scores is (-22) - 0 = -22.
```

## Constraints

- `n == stones.length`
- `2 <= n <= 10^5`
- `-10^4 <= stones[i] <= 10^4`
177 changes: 177 additions & 0 deletions Hard/1872.Stone-Game-VIII/solution.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
# Intuition

The merging rule looks like it creates a complicated evolving row, but it hides a
much smaller game. After any sequence of moves the row is always

```text
[ one merged stone ][ untouched original stones ]
```

and the merged stone's value is exactly the **prefix sum** of every original stone
consumed so far. So the entire position is described by a single number: how far
into the original array the players have eaten.

That gives the key fact. If a player's move ends at original index `j`, the score
they add is `prefix[j]` — the sum of `stones[0..j]` — no matter how the earlier
stones were split between the two players. History does not matter, only `j`.

# Approach: Suffix DP over Prefix Sums

Let `prefix[j]` be the sum of `stones[0..j]` inclusive, and let state `i` mean
"original stones `0..i` are merged into the single leftmost stone". The game starts
at state `0` and ends at state `n - 1`, when one stone remains.

From state `i` the player to move must take at least two stones — the merged one
plus at least one original — so they choose some `j > i`, score `prefix[j]`, and
hand over state `j`. Writing $$f(i)$$ for the best achievable
(current player − opponent) difference from state `i`:

$$f(i) = \max_{j > i} \left( \text{prefix}[j] - f(j) \right), \qquad f(n-1) = 0$$

The subtraction is what makes one formula serve both players: after the move the
opponent becomes "current", so their advantage counts against the mover. The answer
is $$f(0)$$.

## Collapsing to one pass

Evaluated directly this is $$O(n^2)$$. But the set of choices at state `i` is just
the choices at state `i + 1` plus the single new option `j = i + 1`, and the
maximum over that older set is by definition $$f(i)$$'s own neighbour:

$$\max_{j > i+1} \left( \text{prefix}[j] - f(j) \right) = f(i+1)$$

so the recurrence collapses to a two-term maximum:

$$f(i) = \max\left( f(i+1),\; \text{prefix}[i+1] - f(i+1) \right)$$

Now a single running variable suffices. Read it as: *either decline the shortest
move and keep whatever the next state was worth, or take everything through
`i + 1` and pay back the opponent's best reply.*

## Why the loop runs from the right and stops at index 1

Two details in the code follow from the recurrence.

- **Start at `prefix[n-1]`, the full total.** State `n - 2` has exactly one legal
move — take everything — so $$f(n-2) = \text{prefix}[n-1]$$. That is the seed.
- **Stop once `prefix[1]` has been used.** The answer is $$f(0)$$, whose smallest
option is `j = 1`, i.e. taking the first two stones. Indices below that are never
legal targets, because a move must consume more than one stone.

Rather than materialising a prefix array, the code walks the total downward:
starting from `prefix[n-1]` and subtracting `stones[i+1]` leaves exactly
`prefix[i]`. That keeps the whole thing in $$O(1)$$ space. The variable named
`sum` / `total` / `s` in the three versions always holds a **prefix** sum at the
moment it is used.

# Worked example

`stones = [-1, 2, -3, 4, -5]`, so `prefix = [-1, 1, -2, 2, -3]`.

| step | index `i` | running sum = `prefix[i]` | `sum - ans` | new `ans` = $$f(i-1)$$ |
| ---- | --------- | ------------------------- | ----------- | ---------------------- |
| init | — | `prefix[4] = -3` | — | `-3` (this is $$f(3)$$) |
| 1 | 3 | `prefix[3] = 2` | `2 - (-3) = 5` | `max(-3, 5) = 5` |
| 2 | 2 | `prefix[2] = -2` | `-2 - 5 = -7` | `max(5, -7) = 5` |
| 3 | 1 | `prefix[1] = 1` | `1 - 5 = -4` | `max(5, -4) = 5` |

The answer is `5`, matching the statement's walkthrough where Alice scores `2` and
Bob scores `-3`. Step 1 is the move that matters: Alice takes through index `3`
for `prefix[3] = 2`, leaving Bob a position worth `-3` to him.

# Edge cases

- **`n == 2`.** No loop iteration runs, and the answer is the seed `prefix[1]`,
the sum of both stones. Alice has exactly one legal move. Example 3 gives
`-10 + -12 = -22`.
- **All stones negative.** The answer is not simply the total. For `n >= 3` with
every stone `-10`, the result is `+10`: Alice takes the smallest possible bite
and the forced continuations hurt Bob more than her. Verified against a
brute-force search of the full game tree.
- **No 32-bit overflow.** With $$n \le 10^5$$ and $$|stones[i]| \le 10^4$$ the
total is bounded by $$10^9$$, and every intermediate stays there too, roughly
half of `i32`'s range. Checked with adversarial inputs against an `i64` run.

# Complexity

- Time complexity: $$O(n)$$ — one pass to total the array and one pass back down.
- Space complexity: $$O(1)$$ for the Go and Rust versions. The Python version is
$$O(n)$$ because `stones[2:]` copies the tail; iterating indices instead would
make it $$O(1)$$.

# Code

## Go

```go
/* dp[i] is optimial difference score of Alice and Bob: from i -> n - 1
Let's say:
dp[i] = max(dp[i+1], sumOf(i, n - 1) - dp[i+1])
*/
func stoneGameVIII(stones []int) int {
n := len(stones)
sum := 0
for _, stone := range stones {
sum += stone
}
ans := sum
for i := n - 2; i >= 1; i-- {
sum -= stones[i+1]
ans = max(ans, sum - ans)
}
return ans
}
```

The builtin `max` requires Go 1.21 or newer.

## Rust

```rust
impl Solution {
pub fn stone_game_viii(stones: Vec<i32>) -> i32 {
let n = stones.len();
let mut total: i32 = stones.iter().sum();
let mut ans = total;
for i in (1..n-1).rev() {
total -= stones[i+1];
ans = ans.max(total - ans);
}
ans
}
}
```

`(1..n-1).rev()` walks `n-2` down to `1`, matching the Go loop. When `n == 2` the
range `1..1` is empty, so the seed is returned untouched.

## Python

```python
class Solution:
def stoneGameVIII(self, stones: List[int]) -> int:
s = sum(stones)
ans = s
for stone in reversed(stones[2:]):
s -= stone
ans = max(ans, s - ans)
return ans
```

Subtracting `stones[n-1]` down to `stones[2]` produces `prefix[n-2]` down to
`prefix[1]` — the same sequence the indexed loops visit.

# Test cases

| `stones` | answer | note |
| ----------------------- | ------ | ---------------------------------------- |
| `[-1,2,-3,4,-5]` | `5` | Example 1, traced above |
| `[7,-6,5,10,5,-2,-6]` | `13` | Example 2 — Alice takes everything |
| `[-10,-12]` | `-22` | Example 3 — only one legal move |
| `[-10,-10,-10]` | `10` | all-negative, answer is positive |
| `[10000] * 100000` | `10^9` | upper bound on the total |

All three implementations were run against an $$O(n^2)$$ reference derived straight
from the game rules: 20000 random arrays with `n` in `[2, 9]` agree with no
mismatches, and Go, Rust and Python return identical results on a shared corpus of
20003 cases including the three examples.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Easy/350.Intersection-of-Two-Arrays-II/

## Solutions index

Total: **203** problems with at least one solution file.
Total: **204** problems with at least one solution file.

Solution links use variant names when multiple approaches or languages exist (`main` = `solution.md`, others = `solution-<variant>.md`).

Expand Down Expand Up @@ -203,7 +203,7 @@ Solution links use variant names when multiple approaches or languages exist (`m
| 3756. Concatenate Non-Zero Digits and Multiply by Sum II | [Link](https://leetcode.com/problems/concatenate-non-zero-digits-and-multiply-by-sum-ii/) | [main](Medium/3756.Concatenate-Non-Zero-Digits-and-Multiply-by-Sum-II/solution.md) |
| 3867. Sum of GCD of Formed Pairs | [Link](https://leetcode.com/problems/sum-of-gcd-of-formed-pairs/) | [main](Medium/3867.Sum-of-GCD-of-Formed-Pairs/solution.md) |

### Hard (33)
### Hard (34)

| Problem | LeetCode | Solution |
| ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
Expand All @@ -227,6 +227,7 @@ Solution links use variant names when multiple approaches or languages exist (`m
| 1301. Number of Paths with Max Score | [Link](https://leetcode.com/problems/number-of-paths-with-max-score/) | [main](Hard/1301.Number-of-Paths-with-Max-Score/solution.md) |
| 1510. Stone Game IV | [Link](https://leetcode.com/problems/stone-game-iv/) | [main](Hard/1510.Stone-Game-IV/solution.md) |
| 1579. Remove Max Number of Edges to Keep Graph Fully Traversable | [Link](https://leetcode.com/problems/remove-max-number-of-edges-to-keep-graph-fully-traversable/) | [go](Hard/1579.Remove-Max-Number-of-Edges-to-Keep-Graph-Fully-Traversable/solution-go.md) · [rust](Hard/1579.Remove-Max-Number-of-Edges-to-Keep-Graph-Fully-Traversable/solution-rust.md) · [main](Hard/1579.Remove-Max-Number-of-Edges-to-Keep-Graph-Fully-Traversable/solution.md) |
| 1872. Stone Game VIII | [Link](https://leetcode.com/problems/stone-game-viii/) | [main](Hard/1872.Stone-Game-VIII/solution.md) |
| 2071. Maximum Number of Tasks You Can Assign | [Link](https://leetcode.com/problems/maximum-number-of-tasks-you-can-assign/) | [main](Hard/2071.Maximum-Number-of-Tasks-You-Can-Assign/solution.md) |
| 2302. Count Subarrays With Score Less Than K | [Link](https://leetcode.com/problems/count-subarrays-with-score-less-than-k/) | [main](Hard/2302.Count-Subarrays-With-Score-Less-Than-K/solution.md) |
| 2392. Build a Matrix With Conditions | [Link](https://leetcode.com/problems/build-a-matrix-with-conditions/) | [main](Hard/2392.Build-a-Matrix-With-Conditions/solution.md) |
Expand Down
1 change: 1 addition & 0 deletions SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@
* [1301. Number of Paths with Max Score](Hard/1301.Number-of-Paths-with-Max-Score/solution.md)
* [1510. Stone Game IV](Hard/1510.Stone-Game-IV/solution.md)
* [1579. Remove Max Number of Edges to Keep Graph Fully Traversable](Hard/1579.Remove-Max-Number-of-Edges-to-Keep-Graph-Fully-Traversable/solution.md)
* [1872. Stone Game VIII](Hard/1872.Stone-Game-VIII/solution.md)
* [2071. Maximum Number of Tasks You Can Assign](Hard/2071.Maximum-Number-of-Tasks-You-Can-Assign/solution.md)
* [2302. Count Subarrays With Score Less Than K](Hard/2302.Count-Subarrays-With-Score-Less-Than-K/solution.md)
* [2392. Build a Matrix With Conditions](Hard/2392.Build-a-Matrix-With-Conditions/solution.md)
Expand Down
1 change: 1 addition & 0 deletions _sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,7 @@
- [1301. Number of Paths with Max Score](Hard/1301.Number-of-Paths-with-Max-Score/solution.md)
- [1510. Stone Game IV](Hard/1510.Stone-Game-IV/solution.md)
- [1579. Remove Max Number of Edges to Keep Graph Fully Traversable](Hard/1579.Remove-Max-Number-of-Edges-to-Keep-Graph-Fully-Traversable/solution.md)
- [1872. Stone Game VIII](Hard/1872.Stone-Game-VIII/solution.md)
- [2071. Maximum Number of Tasks You Can Assign](Hard/2071.Maximum-Number-of-Tasks-You-Can-Assign/solution.md)
- [2302. Count Subarrays With Score Less Than K](Hard/2302.Count-Subarrays-With-Score-Less-Than-K/solution.md)
- [2392. Build a Matrix With Conditions](Hard/2392.Build-a-Matrix-With-Conditions/solution.md)
Expand Down
Loading