1686. Stone Game VI
Problem Description
Alice and Bob are playing a stone-picking game where they take turns choosing stones from a pile, with Alice going first.
The game setup:
- There are
n
stones in a pile - Each stone has different values for Alice and Bob
aliceValues[i]
represents how much Alice values thei
-th stonebobValues[i]
represents how much Bob values thei
-th stone- When a player picks a stone, they receive points equal to their own valuation of that stone
The game rules:
- Players alternate turns (Alice first, then Bob, then Alice, etc.)
- On each turn, a player removes one stone from the pile and gains points
- Both players play optimally (making the best possible moves)
- Both players know each other's stone valuations
The goal is to determine who wins:
- Return
1
if Alice wins (has more total points) - Return
-1
if Bob wins (has more total points) - Return
0
if it's a draw (equal points)
The key insight is that when playing optimally, each player should consider not just their own gain from picking a stone, but also what they're denying their opponent. A stone with combined value aliceValues[i] + bobValues[i]
represents the total impact of picking that stone - the player gains their value while preventing the opponent from getting their value. Therefore, the optimal strategy is to pick stones in descending order of their combined values, with Alice taking stones at even positions (0, 2, 4, ...) and Bob taking stones at odd positions (1, 3, 5, ...) in this sorted order.
Intuition
When playing optimally, each player needs to think about two things: maximizing their own score and minimizing their opponent's potential score. This is the key to understanding the solution.
Let's think about what happens when Alice picks a stone. She gains aliceValues[i]
points, but equally important, she prevents Bob from potentially getting bobValues[i]
points if he had picked that stone instead. The total impact of Alice picking stone i
is essentially aliceValues[i] + bobValues[i]
- she gains her value and denies Bob his value.
Consider a simple example: Suppose there are two stones left:
- Stone A: Alice values it at 3, Bob values it at 1
- Stone B: Alice values it at 2, Bob values it at 5
If Alice picks Stone A, the net effect is +3 for Alice (she gains 3) and Bob loses the opportunity to gain 1. If Alice picks Stone B, the net effect is +2 for Alice but Bob loses the opportunity to gain 5. Even though Alice gets less direct value from Stone B, picking it has a bigger overall impact because it denies Bob more points.
This leads us to the greedy strategy: stones should be picked in order of their combined value aliceValues[i] + bobValues[i]
. The stone with the highest combined value has the biggest total impact on the game, regardless of who picks it. Since both players play optimally, they will follow this same strategy.
Therefore, we can simulate the optimal game by:
- Sorting all stones by their combined value in descending order
- Having Alice pick the 1st, 3rd, 5th... stones (even indices)
- Having Bob pick the 2nd, 4th, 6th... stones (odd indices)
- Calculating final scores and determining the winner
Learn more about Greedy, Math, Sorting and Heap (Priority Queue) patterns.
Solution Approach
Based on our intuition that stones should be picked by their combined value, we implement the greedy solution as follows:
Step 1: Create value-index pairs
vals = [(a + b, i) for i, (a, b) in enumerate(zip(aliceValues, bobValues))]
We create a list vals
where each element is a tuple (combined_value, index)
. The combined value is aliceValues[i] + bobValues[i]
, representing the total impact of picking stone i
. We keep track of the original index so we know which stone we're referring to after sorting.
Step 2: Sort by combined value
vals.sort(reverse=True)
We sort vals
in descending order by the combined value. This ensures that stones with the highest total impact are picked first, which is the optimal strategy for both players.
Step 3: Simulate optimal play
a = sum(aliceValues[i] for _, i in vals[::2])
b = sum(bobValues[i] for _, i in vals[1::2])
- Alice picks stones at even positions in the sorted list:
vals[0], vals[2], vals[4], ...
(using slice notationvals[::2]
) - Bob picks stones at odd positions:
vals[1], vals[3], vals[5], ...
(using slice notationvals[1::2]
) - For each picked stone, we add the respective player's valuation to their score
Step 4: Determine the winner
if a > b: return 1 if a < b: return -1 return 0
We compare the final scores and return the appropriate result based on who has more points.
Time Complexity: O(n log n)
due to sorting
Space Complexity: O(n)
for storing the vals
array
The key insight is that by sorting stones by their combined value and alternating picks, we simulate the optimal strategy where each player maximizes their gain while denying the opponent the most valuable remaining opportunities.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a concrete example to illustrate the solution approach.
Given:
aliceValues = [2, 4, 3]
bobValues = [1, 6, 7]
Step 1: Create value-index pairs
We calculate the combined value for each stone and pair it with its index:
- Stone 0: combined value = 2 + 1 = 3, pair = (3, 0)
- Stone 1: combined value = 4 + 6 = 10, pair = (10, 1)
- Stone 2: combined value = 3 + 7 = 10, pair = (10, 2)
So vals = [(3, 0), (10, 1), (10, 2)]
Step 2: Sort by combined value (descending)
After sorting: vals = [(10, 1), (10, 2), (3, 0)]
This tells us the optimal picking order is: stone 1, then stone 2, then stone 0.
Step 3: Simulate optimal play
Players alternate picking stones from the sorted list:
-
Turn 1 (Alice): Picks stone at index 1 (first in sorted list)
- Alice gains:
aliceValues[1] = 4
points
- Alice gains:
-
Turn 2 (Bob): Picks stone at index 2 (second in sorted list)
- Bob gains:
bobValues[2] = 7
points
- Bob gains:
-
Turn 3 (Alice): Picks stone at index 0 (third in sorted list)
- Alice gains:
aliceValues[0] = 2
points
- Alice gains:
Step 4: Calculate final scores
- Alice's total: 4 + 2 = 6 points
- Bob's total: 7 points
Since Bob has more points (7 > 6), the function returns -1
.
Why this strategy works: Notice that both stones 1 and 2 had the same combined value of 10. Alice picked stone 1 first because she goes first. Even though she only got 4 points while Bob got 7 points from stone 2, this was still optimal play. If Alice had picked stone 2 instead (getting 3 points), Bob would have then picked stone 1 (getting 6 points), and Alice would end up with stone 0 (2 points), giving her only 3 + 2 = 5 total points - worse than the 6 points she got with optimal play.
Solution Implementation
1class Solution:
2 def stoneGameVI(self, aliceValues: List[int], bobValues: List[int]) -> int:
3 # Create list of (combined_value, index) pairs
4 # Combined value represents the total impact of taking a stone:
5 # - Alice gains aliceValues[i] and denies Bob bobValues[i]
6 # - Bob gains bobValues[i] and denies Alice aliceValues[i]
7 # So the strategic value for either player is the sum of both values
8 combined_values = [(alice_val + bob_val, index)
9 for index, (alice_val, bob_val)
10 in enumerate(zip(aliceValues, bobValues))]
11
12 # Sort by combined value in descending order (greedy approach)
13 # Players should pick stones with highest combined value first
14 combined_values.sort(reverse=True)
15
16 # Alice takes stones at even indices (0, 2, 4, ...)
17 alice_score = sum(aliceValues[index]
18 for _, index in combined_values[::2])
19
20 # Bob takes stones at odd indices (1, 3, 5, ...)
21 bob_score = sum(bobValues[index]
22 for _, index in combined_values[1::2])
23
24 # Determine the winner
25 if alice_score > bob_score:
26 return 1 # Alice wins
27 elif alice_score < bob_score:
28 return -1 # Bob wins
29 else:
30 return 0 # Tie
31
1class Solution {
2 public int stoneGameVI(int[] aliceValues, int[] bobValues) {
3 int n = aliceValues.length;
4
5 // Create array to store combined values and their original indices
6 // Each element: [combined value of alice + bob, original index]
7 int[][] stonesWithPriority = new int[n][2];
8
9 // Calculate priority for each stone (sum of both players' values)
10 // Higher combined value means higher priority to pick
11 for (int i = 0; i < n; i++) {
12 stonesWithPriority[i][0] = aliceValues[i] + bobValues[i];
13 stonesWithPriority[i][1] = i;
14 }
15
16 // Sort stones by combined value in descending order (highest priority first)
17 Arrays.sort(stonesWithPriority, (stone1, stone2) -> stone2[0] - stone1[0]);
18
19 // Track final scores for both players
20 int aliceScore = 0;
21 int bobScore = 0;
22
23 // Players take turns picking stones in order of priority
24 for (int turn = 0; turn < n; turn++) {
25 int stoneIndex = stonesWithPriority[turn][1];
26
27 if (turn % 2 == 0) {
28 // Alice's turn (even turns: 0, 2, 4...)
29 aliceScore += aliceValues[stoneIndex];
30 } else {
31 // Bob's turn (odd turns: 1, 3, 5...)
32 bobScore += bobValues[stoneIndex];
33 }
34 }
35
36 // Determine winner: 1 if Alice wins, -1 if Bob wins, 0 if tie
37 if (aliceScore == bobScore) {
38 return 0;
39 }
40 return aliceScore > bobScore ? 1 : -1;
41 }
42}
43
1class Solution {
2public:
3 int stoneGameVI(vector<int>& aliceValues, vector<int>& bobValues) {
4 // Create a vector to store combined values and their original indices
5 // The key insight: each player should prioritize stones that have high combined value
6 // (their own value + opponent's value) to maximize their gain or minimize opponent's gain
7 vector<pair<int, int>> combinedValues;
8 int n = aliceValues.size();
9
10 // Calculate combined value for each stone and store with its index
11 for (int i = 0; i < n; ++i) {
12 int totalValue = aliceValues[i] + bobValues[i];
13 combinedValues.emplace_back(totalValue, i);
14 }
15
16 // Sort stones by combined value in descending order (greedy strategy)
17 sort(combinedValues.rbegin(), combinedValues.rend());
18
19 // Initialize scores for both players
20 int aliceScore = 0;
21 int bobScore = 0;
22
23 // Players take turns picking stones, starting with Alice (even indices)
24 for (int turn = 0; turn < n; ++turn) {
25 int stoneIndex = combinedValues[turn].second;
26
27 if (turn % 2 == 0) {
28 // Alice's turn: add Alice's value for this stone
29 aliceScore += aliceValues[stoneIndex];
30 } else {
31 // Bob's turn: add Bob's value for this stone
32 bobScore += bobValues[stoneIndex];
33 }
34 }
35
36 // Determine the winner based on final scores
37 if (aliceScore == bobScore) {
38 return 0; // Tie
39 }
40 return aliceScore > bobScore ? 1 : -1; // Alice wins: 1, Bob wins: -1
41 }
42};
43
1/**
2 * Determines the winner of the stone game VI between Alice and Bob.
3 * Each player takes turns choosing stones to maximize their own score.
4 * The strategy is to prioritize stones based on their combined value to both players.
5 *
6 * @param aliceValues - Array of stone values for Alice
7 * @param bobValues - Array of stone values for Bob
8 * @returns 1 if Alice wins, -1 if Bob wins, 0 if tie
9 */
10function stoneGameVI(aliceValues: number[], bobValues: number[]): number {
11 const stonesCount = aliceValues.length;
12
13 // Create array of [combinedValue, originalIndex] pairs
14 // Combined value represents the total importance of each stone to both players
15 const stonePriorities: number[][] = [];
16 for (let i = 0; i < stonesCount; i++) {
17 const combinedValue = aliceValues[i] + bobValues[i];
18 stonePriorities.push([combinedValue, i]);
19 }
20
21 // Sort stones by combined value in descending order
22 // Players should prioritize stones with highest combined value
23 stonePriorities.sort((a, b) => b[0] - a[0]);
24
25 // Initialize scores for both players
26 let aliceScore = 0;
27 let bobScore = 0;
28
29 // Players take turns picking stones based on priority
30 for (let turn = 0; turn < stonesCount; turn++) {
31 const stoneIndex = stonePriorities[turn][1];
32
33 if (turn % 2 === 0) {
34 // Alice's turn (even turns: 0, 2, 4, ...)
35 aliceScore += aliceValues[stoneIndex];
36 } else {
37 // Bob's turn (odd turns: 1, 3, 5, ...)
38 bobScore += bobValues[stoneIndex];
39 }
40 }
41
42 // Determine the winner based on final scores
43 if (aliceScore === bobScore) {
44 return 0; // Tie
45 }
46 return aliceScore > bobScore ? 1 : -1; // Alice wins: 1, Bob wins: -1
47}
48
Time and Space Complexity
Time Complexity: O(n Γ log n)
The time complexity is dominated by the sorting operation. Breaking down the algorithm:
- Creating the
vals
list with combined values takesO(n)
time, where we iterate through both arrays once usingenumerate
andzip
- Sorting the
vals
list takesO(n Γ log n)
time using Python's Timsort algorithm - Computing the sum for Alice's values using list slicing
vals[::2]
takesO(n/2) = O(n)
time - Computing the sum for Bob's values using list slicing
vals[1::2]
takesO(n/2) = O(n)
time - The final comparisons take
O(1)
time
Overall: O(n) + O(n Γ log n) + O(n) + O(n) + O(1) = O(n Γ log n)
Space Complexity: O(n)
The space complexity analysis:
- The
vals
list storesn
tuples, each containing a sum and an index, requiringO(n)
space - The list slicing operations
vals[::2]
andvals[1::2]
create temporary lists for the generator expressions in thesum
functions, but these are evaluated lazily and don't create full lists in memory - The variables
a
andb
useO(1)
space
Overall: O(n) + O(1) = O(n)
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Sorting by Individual Values Instead of Combined Values
The Mistake: A common misconception is to have each player greedily pick stones based solely on their own valuations:
# INCORRECT approach
alice_stones = sorted(enumerate(aliceValues), key=lambda x: x[1], reverse=True)
bob_stones = sorted(enumerate(bobValues), key=lambda x: x[1], reverse=True)
This fails because it ignores the defensive aspect of the game - preventing your opponent from getting high-value stones.
Example where this fails:
aliceValues = [1, 2]
bobValues = [3, 1]
If Alice picks based only on her values, she'd choose stone 1 (value 2). Bob then gets stone 0 (value 3).
- Alice's score: 2
- Bob's score: 3
- Result: Bob wins
But if Alice considers combined values:
- Stone 0: combined value = 1 + 3 = 4
- Stone 1: combined value = 2 + 1 = 3
Alice picks stone 0 first (gets 1 point), Bob picks stone 1 (gets 1 point).
- Result: Tie
The Fix:
Always sort by combined values aliceValues[i] + bobValues[i]
to account for both offensive gain and defensive denial.
Pitfall 2: Incorrect Turn Assignment After Sorting
The Mistake: After sorting by combined values, accidentally assigning the wrong stones to each player:
# INCORRECT - swapping who gets even/odd indices
alice_score = sum(aliceValues[i] for _, i in combined_values[1::2]) # Wrong!
bob_score = sum(bobValues[i] for _, i in combined_values[::2]) # Wrong!
Since Alice goes first, she must get stones at even indices (0, 2, 4, ...) in the sorted list.
The Fix: Remember that Alice always goes first:
- Alice:
combined_values[::2]
(indices 0, 2, 4, ...) - Bob:
combined_values[1::2]
(indices 1, 3, 5, ...)
Pitfall 3: Using Wrong Values When Calculating Scores
The Mistake: Using the combined values for scoring instead of individual player values:
# INCORRECT - using combined value for scoring
alice_score = sum(combined_val for combined_val, _ in combined_values[::2])
The combined value is only for determining pick order. When calculating scores, use each player's individual valuation of the stones they picked.
The Fix: After determining which stones each player picks, use the original value arrays:
alice_score = sum(aliceValues[index] for _, index in combined_values[::2])
bob_score = sum(bobValues[index] for _, index in combined_values[1::2])
Pitfall 4: Not Handling Edge Cases
The Mistake: Not considering edge cases like single stone or empty arrays:
# May fail with index errors on edge cases
if len(aliceValues) == 0:
# Not handled
The Fix: The provided solution naturally handles these cases:
- Single stone: Alice takes it, Bob gets nothing
- Empty array: Both get 0 points (returns 0)
The slicing operations [::2]
and [1::2]
handle these gracefully without additional checks.
Which of the following array represent a max heap?
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Want a Structured Path to Master System Design Too? Donβt Miss This!