1833. Maximum Ice Cream Bars
Problem Description
You're given a scenario where a boy wants to buy ice cream bars on a hot summer day. There are n
ice cream bars available at the store, each with a different price. You have an array costs
of length n
, where costs[i]
represents the price of the i
-th ice cream bar in coins.
The boy starts with a certain number of coins
to spend, and his goal is to buy the maximum number of ice cream bars possible with the money he has. The key point is that he can purchase the ice cream bars in any order he wants.
Your task is to find and return the maximum number of ice cream bars the boy can buy with his available coins
.
For example, if costs = [1, 3, 2, 4, 1]
and coins = 7
, the boy could buy ice cream bars costing 1, 1, 2, and 3 coins (total = 7 coins), giving him 4 ice cream bars in total.
The solution uses a greedy approach: to maximize the number of ice creams purchased, we should buy the cheapest ones first. By sorting the costs array in ascending order and then buying ice creams one by one starting from the cheapest until we run out of money, we can determine the maximum number of ice creams that can be bought.
The algorithm sorts the costs, then iterates through them, subtracting each cost from the available coins. When we encounter an ice cream that costs more than our remaining coins, we stop and return the count of ice creams bought so far. If we can afford all ice creams, we return the total length of the array.
Intuition
When we want to maximize the quantity of items we can buy with a fixed budget, the natural strategy is to prioritize cheaper items first. Think about it like shopping with limited money - if you want to buy as many items as possible, you'd pick the cheapest ones first.
This is a classic greedy problem. The key insight is that since we can buy ice creams in any order and our goal is to maximize the count (not the total value or any other metric), we should always choose the least expensive options available.
Why does this greedy approach work? Consider any optimal solution where we bought k
ice creams. If we didn't buy them in order from cheapest to most expensive, we could swap a more expensive ice cream we bought with a cheaper one we didn't buy. This swap would either:
- Allow us to buy the same number of ice creams with money left over
- Allow us to potentially buy even more ice creams with the saved money
Therefore, sorting the costs and buying from cheapest to most expensive guarantees we get the maximum count.
The implementation becomes straightforward: sort the costs
array, then iterate through it, keeping track of our remaining coins. We keep buying ice creams (subtracting their cost from our coins) until we encounter one we can't afford. The number of ice creams we've bought at that point is our answer. If we can afford all of them, we return the total count.
Solution Approach
The solution implements a greedy algorithm with sorting to maximize the number of ice creams purchased.
Step 1: Sort the costs array
costs.sort()
We sort the costs
array in ascending order. This ensures we can process ice creams from cheapest to most expensive. Python's built-in sort()
uses Timsort algorithm with time complexity O(n log n)
.
Step 2: Iterate and buy ice creams greedily
for i, c in enumerate(costs):
if coins < c:
return i
coins -= c
We iterate through the sorted costs using enumerate()
to keep track of both the index i
and the cost c
:
- For each ice cream, we check if we have enough coins (
coins < c
) - If we don't have enough coins, we immediately return
i
, which represents the number of ice creams bought so far - If we have enough coins, we buy the ice cream by subtracting its cost from our remaining coins (
coins -= c
)
Step 3: Handle the case where all ice creams can be bought
return len(costs)
If the loop completes without returning early, it means we had enough coins to buy all ice creams. In this case, we return the total length of the costs array.
Time Complexity: O(n log n)
due to sorting, where n
is the number of ice creams.
Space Complexity: O(1)
if we don't count the space used by the sorting algorithm (which is typically O(log n)
for the recursion stack in Python's Timsort).
The beauty of this approach is its simplicity - by sorting first and then greedily selecting from the cheapest options, we guarantee the maximum number of purchases without needing complex data structures or dynamic programming.
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 see how the greedy approach works.
Example: costs = [1, 6, 3, 1, 2, 5]
and coins = 7
Step 1: Sort the costs array After sorting in ascending order:
costs = [1, 1, 2, 3, 5, 6]
Step 2: Buy ice creams greedily
Let's trace through each iteration:
-
Iteration 0:
- Current ice cream cost:
1
- Remaining coins:
7
- Can we afford it? Yes (7 โฅ 1)
- Buy it! Coins left:
7 - 1 = 6
- Ice creams bought:
1
- Current ice cream cost:
-
Iteration 1:
- Current ice cream cost:
1
- Remaining coins:
6
- Can we afford it? Yes (6 โฅ 1)
- Buy it! Coins left:
6 - 1 = 5
- Ice creams bought:
2
- Current ice cream cost:
-
Iteration 2:
- Current ice cream cost:
2
- Remaining coins:
5
- Can we afford it? Yes (5 โฅ 2)
- Buy it! Coins left:
5 - 2 = 3
- Ice creams bought:
3
- Current ice cream cost:
-
Iteration 3:
- Current ice cream cost:
3
- Remaining coins:
3
- Can we afford it? Yes (3 โฅ 3)
- Buy it! Coins left:
3 - 3 = 0
- Ice creams bought:
4
- Current ice cream cost:
-
Iteration 4:
- Current ice cream cost:
5
- Remaining coins:
0
- Can we afford it? No (0 < 5)
- Stop and return
4
- Current ice cream cost:
Result: The maximum number of ice creams we can buy is 4
.
We bought ice creams costing [1, 1, 2, 3]
for a total of 7 coins. Notice how by choosing the cheapest options first, we maximized our quantity. If we had bought the expensive ice cream costing 6 coins first, we could only have afforded 2 ice creams total (the 6-coin one and a 1-coin one).
Solution Implementation
1class Solution:
2 def maxIceCream(self, costs: List[int], coins: int) -> int:
3 """
4 Calculate the maximum number of ice cream bars that can be bought with given coins.
5
6 Args:
7 costs: List of integers representing the cost of each ice cream bar
8 coins: Total amount of money available to spend
9
10 Returns:
11 Maximum number of ice cream bars that can be purchased
12 """
13 # Sort costs in ascending order to buy cheapest ice creams first
14 costs.sort()
15
16 # Iterate through sorted costs and buy ice creams while we have enough coins
17 for index, cost in enumerate(costs):
18 # If current ice cream cost exceeds remaining coins, return count
19 if coins < cost:
20 return index
21
22 # Deduct the cost from available coins
23 coins -= cost
24
25 # If we can afford all ice creams, return total count
26 return len(costs)
27
1class Solution {
2 /**
3 * Finds the maximum number of ice cream bars that can be purchased with given coins.
4 * Uses a greedy approach by buying the cheapest ice creams first.
5 *
6 * @param costs Array containing the cost of each ice cream bar
7 * @param coins Total amount of money available to spend
8 * @return Maximum number of ice cream bars that can be purchased
9 */
10 public int maxIceCream(int[] costs, int coins) {
11 // Sort the costs array in ascending order to prioritize cheaper ice creams
12 Arrays.sort(costs);
13
14 // Get the total number of ice cream bars available
15 int numberOfIceCreams = costs.length;
16
17 // Iterate through the sorted costs array
18 for (int i = 0; i < numberOfIceCreams; ++i) {
19 // Check if we have enough coins to buy the current ice cream
20 if (coins < costs[i]) {
21 // If not enough coins, return the count of ice creams bought so far
22 return i;
23 }
24
25 // Deduct the cost of current ice cream from available coins
26 coins -= costs[i];
27 }
28
29 // If we can afford all ice creams, return the total count
30 return numberOfIceCreams;
31 }
32}
33
1class Solution {
2public:
3 int maxIceCream(vector<int>& costs, int coins) {
4 // Sort the costs array in ascending order to buy cheapest ice creams first
5 sort(costs.begin(), costs.end());
6
7 // Get the total number of ice cream bars available
8 int numIceCreams = costs.size();
9
10 // Iterate through sorted costs and buy ice creams while we have enough coins
11 for (int i = 0; i < numIceCreams; ++i) {
12 // If current ice cream costs more than remaining coins, return count
13 if (coins < costs[i]) {
14 return i;
15 }
16
17 // Buy the ice cream by deducting its cost from available coins
18 coins -= costs[i];
19 }
20
21 // If we could afford all ice creams, return the total count
22 return numIceCreams;
23 }
24};
25
1/**
2 * Calculates the maximum number of ice cream bars that can be purchased with given coins
3 * @param costs - Array of ice cream bar prices
4 * @param coins - Total amount of money available
5 * @returns Maximum number of ice cream bars that can be purchased
6 */
7function maxIceCream(costs: number[], coins: number): number {
8 // Sort the costs array in ascending order to buy cheapest ice creams first
9 costs.sort((a: number, b: number) => a - b);
10
11 // Store the total number of ice cream bars available
12 const totalIceCreams: number = costs.length;
13
14 // Iterate through sorted costs and buy ice creams while we have enough coins
15 for (let i: number = 0; i < totalIceCreams; i++) {
16 // Check if we have enough coins for the current ice cream
17 if (coins < costs[i]) {
18 // Not enough coins, return the count of ice creams bought so far
19 return i;
20 }
21
22 // Deduct the cost of current ice cream from available coins
23 coins -= costs[i];
24 }
25
26 // All ice creams were purchased successfully
27 return totalIceCreams;
28}
29
Time and Space Complexity
The time complexity is O(n ร log n)
, where n
is the length of the costs
array. This is dominated by the sorting operation costs.sort()
, which uses Python's Timsort algorithm with O(n ร log n)
complexity in the average and worst cases. The subsequent iteration through the sorted array takes O(n)
time, but this is absorbed by the larger sorting complexity.
The space complexity is O(log n)
. While the sorting operation is performed in-place (modifying the original array), Python's Timsort algorithm requires O(log n)
auxiliary space for its internal operations, specifically for maintaining the stack of pending runs during the merge process. The remaining operations use only O(1)
additional space for variables like i
, c
, and the modified coins
value.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Modifying the Original Input Array
The current solution sorts the costs
array in-place using costs.sort()
, which permanently modifies the original input. This can be problematic if:
- The caller expects the original array to remain unchanged
- The same array is being used elsewhere in the program
- You need to preserve the original order for any reason
Solution: Create a copy of the array before sorting:
def maxIceCream(self, costs: List[int], coins: int) -> int:
# Create a copy to avoid modifying the original array
sorted_costs = sorted(costs) # or costs.copy() then sort
for index, cost in enumerate(sorted_costs):
if coins < cost:
return index
coins -= cost
return len(sorted_costs)
2. Integer Overflow in Other Languages
While Python handles large integers automatically, if implementing this in languages like Java or C++, you might face integer overflow when:
- The sum of costs becomes very large
- The
coins
value is near the maximum integer limit
Solution:
Use appropriate data types (like long
in Java) or add overflow checks:
def maxIceCream(self, costs: List[int], coins: int) -> int:
if coins <= 0: # Handle edge case
return 0
costs.sort()
count = 0
for cost in costs:
if cost > coins: # Check before subtraction
break
coins -= cost
count += 1
return count
3. Not Handling Edge Cases
The current solution doesn't explicitly handle edge cases like:
- Empty
costs
array coins = 0
or negative coins- All ice creams cost more than available coins
Solution: Add explicit edge case handling:
def maxIceCream(self, costs: List[int], coins: int) -> int:
# Handle edge cases
if not costs or coins <= 0:
return 0
costs.sort()
# Early return if cheapest ice cream is unaffordable
if costs[0] > coins:
return 0
for index, cost in enumerate(costs):
if coins < cost:
return index
coins -= cost
return len(costs)
4. Using Wrong Variable in the Loop
A common mistake is accidentally using the wrong variable or forgetting to update the coins:
# WRONG: Forgetting to update coins
for i, c in enumerate(costs):
if coins < c:
return i
# Missing: coins -= c
Solution: Always verify that you're:
- Updating the remaining coins after each purchase
- Using the correct variable names consistently
- Returning the right value (index vs count)
What are the two properties the problem needs to have for dynamic programming to be applicable? (Select 2)
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
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
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Donโt Miss This!