1561. Maximum Number of Coins You Can Get
Problem Description
You are playing a coin collection game with two other people - Alice and Bob. There are 3n
piles of coins with different amounts in each pile.
The game works as follows:
- In each round, you select any 3 piles of coins from the remaining piles
- Alice always takes the pile with the most coins from these 3 piles
- You take the pile with the second-most coins
- Bob gets the pile with the least coins
- This process repeats until all piles are taken
Given an array piles
where piles[i]
represents the number of coins in the i
-th pile, your goal is to find the maximum number of coins you can collect.
For example, if there are 6 piles with coins [2, 4, 1, 2, 7, 8]
, one optimal strategy would be:
- Round 1: Choose piles with
[8, 7, 2]
- Alice gets 8, you get 7, Bob gets 2 - Round 2: Choose piles with
[4, 2, 1]
- Alice gets 4, you get 2, Bob gets 1 - Total coins you get: 7 + 2 = 9
The key insight is that since Alice always takes the maximum and Bob always gets the minimum from each selection of 3 piles, you need to strategically choose which 3 piles to select in each round to maximize your total coins as the person who gets the middle value.
Intuition
Since we always get the second-largest pile from any group of 3 piles we choose, and Alice always gets the largest, we need to think about how to maximize our gains while minimizing what we "waste" on Bob.
The key realization is that Bob is forced to take the smallest pile from each group of 3. So if we want to maximize our coins, we should "sacrifice" the smallest piles to Bob. This way, we can pair ourselves with larger piles.
Think about it this way: if we sort all piles in ascending order, we can give Bob the smallest n
piles (since there are 3n
piles total and we play n
rounds). This leaves us with the largest 2n
piles to split between Alice and ourselves.
From these remaining 2n
piles, Alice will take n
piles and we'll take n
piles. Since Alice always gets the maximum from each group, the best strategy is to pair ourselves as closely as possible with Alice's picks.
For example, with sorted piles [1, 2, 3, 4, 5, 6, 7, 8, 9]
:
- We give Bob:
[1, 2, 3]
(the smallest third) - Remaining for Alice and us:
[4, 5, 6, 7, 8, 9]
- We pair them as:
(9, 8)
,(7, 6)
,(5, 4)
- Alice gets:
[9, 7, 5]
- We get:
[8, 6, 4]
This greedy approach ensures we get the maximum possible coins by taking every second element from the larger 2n
piles, starting from the second-largest pile.
Solution Approach
The implementation follows a greedy strategy with sorting:
-
Sort the array: First, we sort the
piles
array in ascending order. This allows us to easily identify which piles should go to Bob (the smallest ones) and which should be divided between Alice and us (the largest ones). -
Discard the smallest n piles: Since there are
3n
piles total and we playn
rounds, Bob will getn
piles in total. We give him the smallestn
piles by ignoring the first third of the sorted array. This is done withpiles[len(piles) // 3:]
, which takes all elements starting from indexn
(wheren = len(piles) // 3
). -
Take every second element from the remaining piles: From the remaining
2n
largest piles, we need to simulate the pairing where Alice takes the maximum and we take the second maximum from each pair. When these piles are already sorted, this means:- From the last two piles, Alice gets the largest, we get the second-largest
- From the next two piles, Alice gets the larger, we get the smaller
- And so on...
This pattern is achieved by taking every second element starting from index 0 of the remaining array, which is done with
[::2]
slice notation. -
Sum our coins: Finally, we sum all the piles we've collected using
sum()
.
For example, with piles = [1, 2, 3, 4, 5, 6, 7, 8, 9]
:
- After sorting (already sorted):
[1, 2, 3, 4, 5, 6, 7, 8, 9]
- After removing first third:
[4, 5, 6, 7, 8, 9]
- Taking every second element:
[4, 6, 8]
- Sum:
4 + 6 + 8 = 18
The time complexity is O(n log n)
due to sorting, and the space complexity is O(1)
if we don't count the space used by the sorting algorithm.
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 small example with piles = [9, 8, 7, 6, 5, 1]
(6 piles total, so we'll play 2 rounds).
Step 1: Sort the array
After sorting: [1, 5, 6, 7, 8, 9]
Step 2: Identify which piles go to Bob
Since we have 6 piles (3n where n=2), Bob will get 2 piles total (the smallest ones).
We discard the first n=2 piles: [1, 5]
go to Bob.
Remaining piles for Alice and us: [6, 7, 8, 9]
Step 3: Pair up the remaining piles optimally From the remaining 4 piles, we need to form 2 groups of (Alice, You):
- Looking at
[6, 7, 8, 9]
in reverse pairs:- Pair 1:
(9, 8)
- Alice gets 9, you get 8 - Pair 2:
(7, 6)
- Alice gets 7, you get 6
- Pair 1:
Step 4: Extract our piles
Taking every second element from [6, 7, 8, 9]
starting at index 0:
- Index 0: 6 (take)
- Index 1: 7 (skip)
- Index 2: 8 (take)
- Index 3: 9 (skip)
- Our piles:
[6, 8]
Step 5: Calculate the sum
Total coins for us: 6 + 8 = 14
Verification with actual game rounds:
- Round 1: Choose piles
[1, 8, 9]
β Alice: 9, You: 8, Bob: 1 - Round 2: Choose piles
[5, 6, 7]
β Alice: 7, You: 6, Bob: 5 - Your total: 8 + 6 = 14 β
The algorithm correctly maximizes our coins by ensuring Bob gets the smallest piles overall, allowing us to pair with larger values.
Solution Implementation
1class Solution:
2 def maxCoins(self, piles: List[int]) -> int:
3 # Sort the piles in ascending order
4 piles.sort()
5
6 # Calculate the starting index (skip the smallest third of piles)
7 # These will be given to Bob (the opponent)
8 start_index = len(piles) // 3
9
10 # From the remaining piles, take every second element starting from index 0
11 # This ensures we take the second-largest from each triplet
12 # (Alice gets largest, we get second-largest, Bob gets smallest)
13 selected_piles = piles[start_index::2]
14
15 # Return the sum of all selected piles
16 return sum(selected_piles)
17
1class Solution {
2 /**
3 * Calculates the maximum number of coins you can get from piles.
4 *
5 * Strategy: After sorting, form triplets by taking:
6 * - Smallest available pile for Alice (opponent)
7 * - Second largest available pile for you
8 * - Largest available pile for Bob (opponent)
9 *
10 * @param piles Array containing coin piles (length is always 3n)
11 * @return Maximum coins you can collect
12 */
13 public int maxCoins(int[] piles) {
14 // Sort piles in ascending order
15 Arrays.sort(piles);
16
17 // Initialize sum of coins collected
18 int totalCoins = 0;
19
20 // Start from index n (where n = length/3)
21 // Take every second pile (these will be our second-largest in each triplet)
22 // Skip the largest piles (indices at odd positions from our start)
23 for (int index = piles.length / 3; index < piles.length; index += 2) {
24 totalCoins += piles[index];
25 }
26
27 return totalCoins;
28 }
29}
30
1class Solution {
2public:
3 int maxCoins(vector<int>& piles) {
4 // Sort the piles in ascending order
5 ranges::sort(piles);
6
7 // Initialize the result variable to store maximum coins
8 int result = 0;
9
10 // Start from index n/3 and pick every second element
11 // This ensures we get the second largest from each triplet
12 // We skip the first n/3 elements (smallest values for Bob)
13 // and pick alternating elements (second largest for us)
14 for (int i = piles.size() / 3; i < piles.size(); i += 2) {
15 result += piles[i];
16 }
17
18 return result;
19 }
20};
21
1/**
2 * Calculates the maximum number of coins you can get from piles.
3 * In each round, 3 piles are chosen: Alice takes the maximum, you take the second maximum,
4 * and Bob takes the minimum. The goal is to maximize your coins.
5 *
6 * @param piles - Array of coin piles where length is divisible by 3
7 * @returns Maximum number of coins you can obtain
8 */
9function maxCoins(piles: number[]): number {
10 // Sort piles in ascending order
11 piles.sort((a: number, b: number) => a - b);
12
13 // Initialize the total coins collected
14 let totalCoins: number = 0;
15
16 // Start from the position after Bob's piles (first n/3 elements)
17 // Take every second pile (you get the second largest in each triplet)
18 const startIndex: number = piles.length / 3;
19 for (let i: number = startIndex; i < piles.length; i += 2) {
20 totalCoins += piles[i];
21 }
22
23 return totalCoins;
24}
25
Time and Space Complexity
The time complexity is O(n Γ log n)
, where n
is the number of piles of coins. This is dominated by the piles.sort()
operation, which uses a comparison-based sorting algorithm (typically Timsort in Python) that has O(n Γ log n)
time complexity. The subsequent slicing and summation operations take O(n)
time, but this is absorbed by the larger sorting complexity.
The space complexity is O(log n)
. While the slicing operation piles[len(piles) // 3:][::2]
creates a new list that takes O(n)
space, the dominant space consideration for complexity analysis is the auxiliary space used by the sorting algorithm. Python's Timsort uses O(log n)
space for its recursive call stack during the sorting process. The temporary space for the slice is typically not counted in the space complexity analysis when considering the algorithm's auxiliary space requirements.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Misunderstanding the Selection Strategy
The Problem: Many people initially think they need to simulate the actual game rounds by selecting specific triplets of piles. They might try to implement complex logic to choose which 3 piles to select in each round, attempting to maximize their coins while considering what Alice and Bob will get.
Why It Happens: The problem statement describes the game procedurally (selecting 3 piles each round), which leads to overthinking the implementation as a simulation.
The Solution: Recognize that you have complete control over which triplets are formed. The optimal strategy is straightforward: give Bob all the smallest piles, then pair up the remaining piles so you get the second-largest from each pair. This translates to the sorted array approach without needing to explicitly form triplets.
Pitfall 2: Incorrect Index Calculation After Sorting
The Problem: After sorting, some might incorrectly calculate which elements to take. Common mistakes include:
- Taking elements from index 0 with step 2 from the entire sorted array:
piles[::2]
- Taking the wrong slice after removing Bob's piles:
piles[len(piles)//3::2]
vspiles[len(piles)//3:][::2]
Why It Happens: Confusion about slice notation and how to properly skip Bob's piles before selecting every second element.
The Solution: Break it down into two clear steps:
- First, remove Bob's n smallest piles:
remaining = piles[len(piles)//3:]
- Then, take every second element from the remaining:
remaining[::2]
Or combine them carefully: piles[len(piles)//3::2]
(starting from index n with step 2).
Pitfall 3: Off-by-One Errors in Alternative Approaches
The Problem: When implementing an alternative approach that iterates through pairs from the end of the sorted array, it's easy to make off-by-one errors:
# Incorrect - might access wrong indices
result = 0
for i in range(len(piles)-2, len(piles)//3, -2):
result += piles[i]
Why It Happens: Working backwards through arrays with specific step sizes requires careful calculation of start and end indices.
The Solution: Use the cleaner slice-based approach, or if iterating, carefully verify the loop bounds:
# Correct iteration approach
result = 0
n = len(piles) // 3
for i in range(n):
# Take the second-largest from each pair at the end
result += piles[len(piles) - 2 - 2*i]
Pitfall 4: Not Considering Edge Cases
The Problem: Failing to consider minimum constraints like when piles
has exactly 3 elements (n=1), leading to potential index errors or incorrect logic.
Why It Happens: The algorithm seems straightforward for larger inputs, making it easy to overlook boundary cases.
The Solution: The given approach naturally handles all valid cases since:
- For n=1 (3 piles): We skip 1 pile for Bob, take element at index 1 (the middle value)
- The slice notation
[start::2]
works correctly even for small arrays - The problem guarantees
len(piles) % 3 == 0
andlen(piles) >= 3
How would you design a stack which has a function min
that returns the minimum element in the stack, in addition to push
and pop
? All push
, pop
, min
should have running time O(1)
.
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!