1872. Merge Stone Game
Stone Game VIII
Problem 1872 is a DP problem that creates a particularly deep impression on me.
LeetCode's super popular Alice & Bob game.
Alice Always Goes First
As LeetCode's tradition, Alice moves first just like ladies first.
So let's put ourselves in Alice's shoes and think about her opening move.
The simplest first move is for Alice to merge all stones into one in 1 sweep, bringing the game to an end with only one stone remaining.
Alice earns points this way.
Bob, having no turn, scores 0 of course.
Alice's sweep gives a score difference of ,
where the subscript indicates this merge included the stone.
But What If There Are Stones?
Other Options Arise
With stones, Alice has more than just that one-and-done sweep.
She can also leave out some stones, letting Bob come up to merge and score.
Suppose Alice chooses to merge only up to the stone.
She earns points.
Two stones remain on the table: the rightmost with value ,
and the merged one Alice just created with value .
Now Bob must merge both (no passing allowed), earning:
The score difference is .
Notably, can be rewritten using as:
Should Alice Switch Options?
Now we have a binary choice: vs. .
(1). If , Alice abandons that one-and-done merger,
and chooses to merge only up to stone, leaving out .
Recall
is equivalent to
One conversation to describe: "To my dear opponent: I'll give you .
That's because I have an even stronger option ~~ so strong that even after your offset, my net differential still exceeds what I'd get if taking directly."
(2). Conversely, if , Alice merges everything.
From the same equation: means
In translation: "Merging only up to stone earns me ,
but my opponent earns . After offsetting, my net lead is .
Comparing these two, I find out it's better to also include stone in my merger,
taking as my net differential."
Because of this reasoning, must be assigned the value of ,
reflecting a key insight: when only and stones remain unmerged,
based on the score comparison, merging both is better than leaving out stone.
So the option to stop at stone is eliminated. This elimination is due to our problem's own rule: each player plays optimally.
Deriving the Transition from Here
By the same logic, if there's an option to merge only up to stone:
This gives us a binary choice between and .
You've probably spotted the pattern. The transition equation is:
where the index range is . Why this range?
(1). Index 0 is excluded because the problem requires merging at least 2 stones each time.
(2). Index is also excluded because it's the base case:
merging all stones at once, covering up to the stone.
Base Case:
Understanding the Transition
I once found a description that captures the stories behind quite well:
Given that when only stones remain, the current player will earn a net differential of .
If I merge only up to stone and earn ,
can my earning still beat after being offset?
I. If yes: I'm willing to play a bigger game by seemingly making a sacrifice here.
Give up the known gain in exchange for an even greater upcoming gain.
II. If no: I can't let my opponent take that .
Better put it into my pocket now to be my net differential.
You'll also notice our transition here is a bit unusual:
it moves from larger indices to smaller indices.
So when computing, traverse from larger to smaller indices,
rather than using extra DFS and cache which would cost space......
Also, the transition equation tells us each index's state only depends on the immediately previous index, so there's no need to maintain an array costing space.
- C++
- Python
#include <numeric>
#include <vector>
using namespace std;
int maximizeScoresDifference(vector<int>& stones) // LeetCode Q.1872.
{
int prefixSum = 0;
for (const auto& stone : stones)
prefixSum += stone;
// Base case: Alice merges everything as the only attempt.
int maxScoreDiff = prefixSum;
prefixSum -= stones.back();
for (int idx = stones.size() - 2; idx >= 1; idx--) {
if (prefixSum - maxScoreDiff > maxScoreDiff)
maxScoreDiff = prefixSum - maxScoreDiff;
prefixSum -= stones[idx];
}
return maxScoreDiff;
}
def maximize_scores_difference(stones: list[int]) -> int: # LeetCode Q.1872.
prefix_sum = sum(stones)
# Base case: Alice merges everything as the only attempt.
max_score_diff = prefix_sum
prefix_sum -= stones[-1]
for idx in range(len(stones) - 2, 0, -1):
if prefix_sum - max_score_diff > max_score_diff:
max_score_diff = prefix_sum - max_score_diff
prefix_sum -= stones[idx]
return max_score_diff
Just use a variable called max_score_diff to track .

Time complexity , space complexity .
Follow-up Problems
I. How would you modify the code above to print out all "scoring plays"?
A scoring play is defined as when any player merges some stones and earns points.
II. To make the game more exciting, our goal is to maximize the number of scoring plays.
What input array stones should you design to achieve this?