diff --git a/Hard/1872.Stone-Game-VIII/description.md b/Hard/1872.Stone-Game-VIII/description.md new file mode 100644 index 0000000..7ba486a --- /dev/null +++ b/Hard/1872.Stone-Game-VIII/description.md @@ -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` diff --git a/Hard/1872.Stone-Game-VIII/solution.md b/Hard/1872.Stone-Game-VIII/solution.md new file mode 100644 index 0000000..351da9f --- /dev/null +++ b/Hard/1872.Stone-Game-VIII/solution.md @@ -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 { + 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. diff --git a/README.md b/README.md index cd23411..1071788 100644 --- a/README.md +++ b/README.md @@ -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-.md`). @@ -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 | | ---------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | @@ -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) | diff --git a/SUMMARY.md b/SUMMARY.md index 6aeb250..e4e72b4 100644 --- a/SUMMARY.md +++ b/SUMMARY.md @@ -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) diff --git a/_sidebar.md b/_sidebar.md index b014fea..5dc7e01 100644 --- a/_sidebar.md +++ b/_sidebar.md @@ -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)