1753. Maximum Score From Removing Stones
Problem Description
You have three piles of stones with sizes a
, b
, and c
. In each turn of the game, you must:
- Choose two different non-empty piles
- Remove one stone from each of the two chosen piles
- Add 1 point to your score
The game continues until you cannot make a valid move anymore (when fewer than two piles have stones remaining).
Your goal is to find the maximum possible score you can achieve by making optimal moves.
For example, if you have piles of sizes a = 2
, b = 4
, and c = 6
:
- You could take stones from piles
b
andc
, leaving you with(2, 3, 5)
and scoring 1 point - Continue taking from the two largest piles to maximize your score
- The game ends when at most one pile has stones remaining
The solution approach repeatedly takes stones from the two largest piles. By sorting the piles after each move and always choosing the two largest piles (indices 1 and 2 in the sorted array), we ensure we can make the maximum number of moves possible. The algorithm continues until the middle pile becomes empty, at which point we can no longer make valid moves since we need at least two non-empty piles.
Intuition
The key insight is to think about when the game ends and work backwards. The game stops when we have at most one non-empty pile. This means we want to distribute our moves in a way that uses up the stones as evenly as possible.
Consider what happens if one pile is much larger than the others. If pile c
is huge while a
and b
are small, we'll quickly exhaust a
and b
, leaving many stones unused in c
. This limits our score.
The optimal strategy is to always take stones from the two largest piles. Why? By repeatedly reducing the largest piles, we prevent any single pile from becoming too dominant. This keeps all piles relatively balanced, allowing us to continue making moves for as long as possible.
Think of it like balancing water levels in three containers - if we always drain from the two fullest containers, we maximize the time before one runs dry.
Mathematically, the maximum score is actually min(sum(a, b, c) / 2, a + b + c - max(a, b, c))
. The first part sum/2
represents the theoretical maximum if we could perfectly pair all stones. The second part handles the case where one pile is larger than the sum of the other two - in this case, we're limited by the smaller piles.
The greedy approach of always choosing the two largest piles naturally achieves this maximum. By sorting after each move and selecting piles at indices 1 and 2, we ensure we're always making the optimal choice that keeps the piles as balanced as possible.
Learn more about Greedy, Math and Heap (Priority Queue) patterns.
Solution Approach
The implementation uses a greedy algorithm with sorting to maintain the optimal selection of piles at each step.
Data Structure: We use a list s
to store the three pile sizes, which allows us to sort them repeatedly.
Algorithm Steps:
-
Initialize: Create a sorted list
s = sorted([a, b, c])
so thats[0] ≤ s[1] ≤ s[2]
. This ensures we have the piles ordered from smallest to largest. -
Main Loop: Continue while
s[1] > 0
(the middle pile is non-empty):- Why check
s[1]
? If the middle pile is empty, then the smallest piles[0]
must also be empty (since they're sorted), meaning we have at most one non-empty pile and cannot make valid moves.
- Why check
-
Make a Move: In each iteration:
- Increment the answer counter:
ans += 1
- Remove one stone from the two largest piles:
s[1] -= 1
ands[2] -= 1
- Re-sort the list:
s.sort()
- Increment the answer counter:
-
Re-sorting Logic: After taking stones from piles at positions 1 and 2, the ordering might change. For example, if we had
[1, 3, 5]
and took from the larger two, we'd get[1, 2, 4]
. The sorting ensures we always identify the correct two largest piles for the next move. -
Return: When the loop exits (middle pile becomes 0), return
ans
as the maximum score.
Time Complexity: O(n log n)
where n
is the total number of moves (maximum of (a+b+c)/2
). Each iteration involves sorting 3 elements, which is O(1)
, but we do this n
times.
Space Complexity: O(1)
as we only use a fixed-size list of 3 elements.
The beauty of this approach is its simplicity - by always maintaining sorted order and greedily selecting the two largest piles, we automatically achieve the maximum possible score without complex calculations.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through the algorithm with piles a = 2
, b = 4
, and c = 6
.
Initial Setup:
- Start with
s = sorted([2, 4, 6]) = [2, 4, 6]
- Score = 0
Move 1:
- Check:
s[1] = 4 > 0
✓ (can continue) - Take from two largest piles:
s[1] = 4-1 = 3
,s[2] = 6-1 = 5
- Current state:
[2, 3, 5]
- Sort:
[2, 3, 5]
(already sorted) - Score = 1
Move 2:
- Check:
s[1] = 3 > 0
✓ - Take from two largest:
s[1] = 3-1 = 2
,s[2] = 5-1 = 4
- Current state:
[2, 2, 4]
- Sort:
[2, 2, 4]
- Score = 2
Move 3:
- Check:
s[1] = 2 > 0
✓ - Take from two largest:
s[1] = 2-1 = 1
,s[2] = 4-1 = 3
- Current state:
[2, 1, 3]
- Sort:
[1, 2, 3]
- Score = 3
Move 4:
- Check:
s[1] = 2 > 0
✓ - Take from two largest:
s[1] = 2-1 = 1
,s[2] = 3-1 = 2
- Current state:
[1, 1, 2]
- Sort:
[1, 1, 2]
- Score = 4
Move 5:
- Check:
s[1] = 1 > 0
✓ - Take from two largest:
s[1] = 1-1 = 0
,s[2] = 2-1 = 1
- Current state:
[1, 0, 1]
- Sort:
[0, 1, 1]
- Score = 5
Move 6:
- Check:
s[1] = 1 > 0
✓ - Take from two largest:
s[1] = 1-1 = 0
,s[2] = 1-1 = 0
- Current state:
[0, 0, 0]
- Sort:
[0, 0, 0]
- Score = 6
Termination:
- Check:
s[1] = 0
(not > 0) ✗ - Game ends with final score = 6
Notice how by always taking from the two largest piles, we avoided depleting any pile too quickly. This balanced approach allowed us to make 6 moves total, which equals (2+4+6)/2 = 6
, the theoretical maximum.
Solution Implementation
1class Solution:
2 def maximumScore(self, a: int, b: int, c: int) -> int:
3 """
4 Calculate the maximum score by repeatedly taking one stone from two different piles.
5 Each operation removes one stone from two piles and adds 1 to the score.
6
7 Args:
8 a: Number of stones in pile A
9 b: Number of stones in pile B
10 c: Number of stones in pile C
11
12 Returns:
13 Maximum possible score
14 """
15 # Sort the three piles in ascending order
16 piles = sorted([a, b, c])
17 score = 0
18
19 # Keep taking stones from the two largest piles until the middle pile is empty
20 # This ensures we maximize the number of operations
21 while piles[1] > 0:
22 score += 1
23 piles[1] -= 1 # Take one stone from the middle pile
24 piles[2] -= 1 # Take one stone from the largest pile
25
26 # Re-sort to maintain ascending order
27 piles.sort()
28
29 return score
30```
31
32Actually, there's a mathematical optimization we can apply here. The maximum score is either the sum of the two smaller piles (if they together are less than or equal to the largest), or half of the total sum:
33
34```python3
35class Solution:
36 def maximumScore(self, a: int, b: int, c: int) -> int:
37 """
38 Calculate the maximum score by repeatedly taking one stone from two different piles.
39
40 The optimal strategy is to always take from the two largest piles.
41 The maximum score is min(sum//2, sum - max_pile) where sum is the total stones.
42
43 Args:
44 a: Number of stones in pile A
45 b: Number of stones in pile B
46 c: Number of stones in pile C
47
48 Returns:
49 Maximum possible score
50 """
51 # Calculate total sum and find the maximum pile
52 total_sum = a + b + c
53 max_pile = max(a, b, c)
54
55 # The answer is the minimum of:
56 # 1. Half of total stones (best case scenario)
57 # 2. Sum of two smaller piles (when largest pile is too big)
58 return min(total_sum // 2, total_sum - max_pile)
59
1class Solution {
2 public int maximumScore(int a, int b, int c) {
3 // Create an array to store the three pile sizes
4 int[] piles = new int[] {a, b, c};
5
6 // Sort the piles in ascending order
7 Arrays.sort(piles);
8
9 // Initialize the counter for number of operations
10 int operationCount = 0;
11
12 // Continue while we have at least two non-empty piles
13 // piles[1] > 0 ensures both piles[1] and piles[2] are non-empty
14 // since array is sorted
15 while (piles[1] > 0) {
16 // Increment the operation counter
17 operationCount++;
18
19 // Remove one stone from the two largest piles
20 piles[1]--;
21 piles[2]--;
22
23 // Re-sort to maintain ascending order for next iteration
24 Arrays.sort(piles);
25 }
26
27 // Return the total number of operations performed
28 return operationCount;
29 }
30}
31
1class Solution {
2public:
3 int maximumScore(int a, int b, int c) {
4 // Store the three pile sizes in a vector for easier manipulation
5 vector<int> piles = {a, b, c};
6
7 // Sort piles in ascending order: piles[0] <= piles[1] <= piles[2]
8 sort(piles.begin(), piles.end());
9
10 int score = 0;
11
12 // Continue taking stones while the two smallest piles both have stones
13 // We always take from the two largest piles after sorting
14 while (piles[1] > 0) {
15 // Increment score for each valid operation
16 ++score;
17
18 // Remove one stone from the two largest piles
19 piles[1]--;
20 piles[2]--;
21
22 // Re-sort to maintain the invariant: piles[0] <= piles[1] <= piles[2]
23 sort(piles.begin(), piles.end());
24 }
25
26 return score;
27 }
28};
29
1function maximumScore(a: number, b: number, c: number): number {
2 // Store the three pile sizes in an array for easier manipulation
3 let piles: number[] = [a, b, c];
4
5 // Sort piles in ascending order: piles[0] <= piles[1] <= piles[2]
6 piles.sort((x, y) => x - y);
7
8 let score: number = 0;
9
10 // Continue taking stones while the two smallest piles both have stones
11 // We always take from the two largest piles after sorting
12 while (piles[1] > 0) {
13 // Increment score for each valid operation
14 score++;
15
16 // Remove one stone from the two largest piles
17 piles[1]--;
18 piles[2]--;
19
20 // Re-sort to maintain the invariant: piles[0] <= piles[1] <= piles[2]
21 piles.sort((x, y) => x - y);
22 }
23
24 return score;
25}
26
Time and Space Complexity
Time Complexity: O(min(a, b, c) * log 3)
which simplifies to O(min(a, b, c))
The code uses a while loop that continues until s[1]
becomes 0. In each iteration:
- Two elements are decremented by 1 (
s[1]
ands[2]
) - The array is sorted using
s.sort()
, which takesO(log 3) = O(1)
time for a fixed-size array of 3 elements
The loop runs at most min(a, b, c)
times because:
- After sorting,
s[0] ≤ s[1] ≤ s[2]
- Each iteration decrements the two larger values
- The loop terminates when the middle element
s[1]
reaches 0 - The maximum number of iterations occurs when we can pair the smallest pile with others until it's exhausted
Therefore, the overall time complexity is O(min(a, b, c) * 1) = O(min(a, b, c))
Space Complexity: O(1)
The code uses:
- A fixed-size list
s
containing 3 elements - A single integer variable
ans
No additional space that scales with input size is used, resulting in constant space complexity O(1)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Re-sort After Each Move
One of the most common mistakes is taking stones from the two largest piles but forgetting to re-sort the array afterward. This leads to incorrect pile selection in subsequent iterations.
Incorrect Implementation:
def maximumScore(self, a: int, b: int, c: int) -> int:
piles = sorted([a, b, c])
score = 0
while piles[1] > 0:
score += 1
piles[1] -= 1
piles[2] -= 1
# MISSING: piles.sort() - This will cause wrong selections!
return score
Why it fails: After removing stones, the relative sizes change. For example, starting with [2, 5, 6]
, after one move we have [2, 4, 5]
. Without sorting, the next iteration would incorrectly modify indices 1 and 2 again, giving [2, 3, 4]
, when it should select the actual two largest piles.
Solution: Always sort after each modification to maintain the invariant that piles[0] ≤ piles[1] ≤ piles[2]
.
2. Incorrect Termination Condition
Another pitfall is using the wrong condition to determine when the game ends.
Incorrect Implementations:
# Wrong: Checking only the largest pile while piles[2] > 0: # Game might end earlier! # Wrong: Checking the smallest pile while piles[0] > 0: # Game continues even after this! # Wrong: Checking if we can make exactly one more move while piles[1] > 0 and piles[2] > 0: # Redundant check
Why checking piles[1] > 0
is correct: When the middle pile becomes 0, we know:
- The smallest pile
piles[0]
must also be 0 (due to sorting) - We have at most one non-empty pile (the largest)
- We cannot make valid moves with fewer than 2 non-empty piles
3. Attempting to Optimize by Taking From Different Piles
Some might think taking from the smallest and largest pile could be beneficial in certain cases to "balance" the piles.
Incorrect Strategy:
def maximumScore(self, a: int, b: int, c: int) -> int:
piles = sorted([a, b, c])
score = 0
while piles[1] > 0:
score += 1
# Wrong: Trying to "balance" by taking from smallest and largest
if piles[2] - piles[0] > piles[1]:
piles[0] -= 1
piles[2] -= 1
else:
piles[1] -= 1
piles[2] -= 1
piles.sort()
return score
Why it's wrong: The greedy approach of always taking from the two largest piles is mathematically optimal. Trying to balance piles can actually reduce the total number of moves possible.
4. Integer Overflow in Mathematical Solution
When using the optimized mathematical formula, be careful with the order of operations.
Potential Issue:
# In some languages, this might overflow for large values
return min((a + b + c) // 2, a + b + c - max(a, b, c))
Better Approach:
def maximumScore(self, a: int, b: int, c: int) -> int:
total = a + b + c
max_pile = max(a, b, c)
# Store intermediate results to avoid recalculation
return min(total // 2, total - max_pile)
5. Not Handling Edge Cases
While the problem typically guarantees non-negative inputs, it's good practice to consider edge cases:
Robust Implementation:
def maximumScore(self, a: int, b: int, c: int) -> int:
# Handle edge case where all piles are 0
if a == 0 and b == 0 and c == 0:
return 0
# Handle case where only one pile has stones
non_zero_count = sum(1 for x in [a, b, c] if x > 0)
if non_zero_count < 2:
return 0
# Proceed with normal algorithm
total = a + b + c
max_pile = max(a, b, c)
return min(total // 2, total - max_pile)
What's the relationship between a tree and a graph?
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
https assets algo monster cover_photos heap svg Priority Queue and Heap What is the relationship between priority queue and heap Priority Queue is an Abstract Data Type and Heap is the concrete data structure we use to implement a priority queue Priority Queue A priority queue is a data structure
Want a Structured Path to Master System Design Too? Don’t Miss This!