2144. Minimum Cost of Buying Candies With Discount
Problem Description
In this problem, we are working with a candy shop that has a particular discount scheme: for every two candies purchased, a third candy is given for free. The caveat is that the cost of the free candy can't exceed the cost of the least expensive candy bought among the two. We need to find out how much the customer will have to pay in total to buy all the candies available in the shop considering this discount.
The given data is an integer array cost
which represents the price of each candy. The array is 0-indexed, meaning that the first element is at index 0, the second at index 1, and so on. Our task is to calculate the minimum total cost required to buy all candies while making the most out of the given discount.
Intuition
To minimize the total cost of buying all the candies while taking advantage of the discount, we should aim to get the more expensive candies for free when possible. Hence, the best strategy would be to sort the candies in descending order of their cost. That way, every time we pick two candies, they will be the most expensive ones available, and we can then choose one of the less expensive candies for free (following the rule that the free candy must be priced less than or equal to the cheaper of the two paid candies).
After sorting, we can observe that in every three consecutive candies starting from the first (index 0), the third one (at index 2) would be the free one if we adopt the purchasing pattern of buying in sets of three, so to speak. We continue this pattern throughout the array. Thus, by taking the sum of all candies' costs and subtracting the sum of every third candy post-sorting, we get the total minimum cost. This is exactly what the provided solution is doing: it sorts the list in reverse order, then sums up all elements, excluding every third element starting by the third (index 2, as the index is zero-based). That is the sum of all the costs of the free candies if we buy them in an optimal way.
Solution Approach
The solution approach takes advantage of sorting and list slicing operations. Here's a step-by-step explanation of the implementation:
-
Sorting the list in reverse order: We use the
sort
method with thereverse=True
parameter to rearrange the elements of thecost
list in descending order. Sorting is a common algorithmic pattern when we need to rearrange data for optimization purposes, such as minimizing or maximizing some value subject to constraints—in this case, the constraint is the "buy two, get one free" deal.cost.sort(reverse=True)
After this operation, the most expensive candies are at the beginning of the list, and the least expensive ones are at the end.
-
Calculating the total sum: The
sum
function calculates the total cost of the candies if there were no discounts. This serves as our starting point for finding the minimum cost.total_cost = sum(cost)
-
Identifying the free candies using slicing: The third step uses list slicing to identify all the candies that would be free. The slice operation
cost[2::3]
takes every third candy from the list, starting with the one at index 2 (zero-based index, which is the third candy). This exploits a pattern that emerges from sorting the candies by cost: when they are sorted in descending order, each set of three contains two paid candies followed by one free candy.free_candies_cost = sum(cost[2::3])
-
Subtracting the cost of free candies: By subtracting the sum of the free candies' costs from the total cost, we arrive at the minimum cost to buy all candies. This effectively applies the "buy two, get one free" discount across the entire list.
minimum_cost = total_cost - free_candies_cost
Putting it all together, the provided solution is a compact representation of these steps, combining the total calculation and the subtraction of the free candies' costs into a single line:
return sum(cost) - sum(cost[2::3])
This approach is both elegant and efficient, as it avoids the need for explicit loops or additional data structures. The sorting algorithm behind the sort
method typically has O(n log n) complexity, where n
is the number of candies, and the slicing operation has O(n) complexity. Thus, the overall complexity of the solution is dominated by the sorting step.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's suppose we have the following candy prices:
cost = [1, 3, 2, 8, 5, 7]
To understand the solution approach, let's walk through it step by step using this example:
-
Sorting the list in reverse order: We sort the
cost
list in descending order to prioritize the purchase of the most expensive candies first.cost.sort(reverse=True) // After sorting: cost = [8, 7, 5, 3, 2, 1]
-
Calculating the total sum: Before applying the discount, calculate the total cost of all candies.
total_cost = sum(cost) // total_cost = 8 + 7 + 5 + 3 + 2 + 1 = 26
-
Identifying the free candies using slicing: According to the discount, for every two candies purchased, the third one is free, starting from the least expensive of the paid ones. To find the free candies applying the discount optimally, we take every third candy in the sorted list, beginning with the one at index 2.
free_candies_cost = sum(cost[2::3]) // The free candies are at indices 2 and 5, so free_candies_cost = 5 + 1 = 6
-
Subtracting the cost of free candies: Finally, to get the minimum cost after the discount, subtract the total cost of free candies from the total cost.
minimum_cost = total_cost - free_candies_cost // minimum_cost = 26 - 6 = 20
So, the minimum total cost to buy all candies while taking full advantage of the discounts is 20.
Using our approach, we've paired the most expensive candies first ([8, 7], [5, 3]), then we get the least expensive candies as the free items ([2] and [1]). The 2 and 1 correspond to the third items in each buying set, which are the free items according to our sorted cost
list.
By following these steps, the provided solution effectively minimizes the total cost, ensuring that customers get the highest-priced candies possible for free, in compliance with the shop's discount policy.
Solution Implementation
1class Solution:
2 def minimumCost(self, costs: List[int]) -> int:
3 # Sort the list of costs in descending order
4 costs.sort(reverse=True)
5
6 # Initialize the minimum cost to the sum of all costs
7 min_cost = sum(costs)
8
9 # Subtract the cost of every third item starting from the third item
10 # because it would be the free item as per the typical "buy two, get one free" offer.
11 min_cost -= sum(costs[2::3])
12
13 # Return the calculated minimum cost
14 return min_cost
15
1class Solution {
2
3 /**
4 * Calculates the minimum cost to purchase all the items given that
5 * every third item is free after sorting the array in non-decreasing order.
6 *
7 * @param costs Array of individual costs of items.
8 * @return The minimum total cost to purchase all items.
9 */
10 public int minimumCost(int[] costs) {
11 // Sort the array of costs in non-decreasing order.
12 Arrays.sort(costs);
13
14 int totalCost = 0; // Initialize total cost to 0.
15 // Start purchasing items from the most expensive one.
16 for (int i = costs.length - 1; i >= 0; i -= 3) {
17 totalCost += costs[i]; // Purchase the most expensive item.
18 if (i > 0) { // Check if there is a second item to purchase.
19 totalCost += costs[i - 1]; // Purchase the second item.
20 }
21 }
22 return totalCost; // Return the total minimum cost.
23 }
24}
25
1#include <vector>
2#include <algorithm> // Included for std::sort
3
4class Solution {
5public:
6 /**
7 * Calculate the minimum cost of buying items based on the offer to pay for two
8 * items and get one free, optimizing to always get the most expensive item for free.
9 *
10 * @param costs Vector of individual item costs.
11 * @return The minimum total cost with the offer applied.
12 */
13 int minimumCost(vector<int>& costs) {
14 // Sort the costs in descending order to prioritize the most expensive items.
15 std::sort(costs.rbegin(), costs.rend());
16
17 int totalCost = 0; // Initialize total cost to 0
18
19 // Iterate over every three items to apply the "buy two get one free" offer
20 for (int i = 0; i < costs.size(); i += 3) {
21 totalCost += costs[i]; // Always add the cost of the first item (most expensive)
22
23 // Check if there is a second item to also add its cost
24 if (i + 1 < costs.size()) {
25 totalCost += costs[i + 1]; // Add the cost of the second item
26 }
27
28 // The third item is free, so it is not added to the total
29 }
30
31 // Return the calculated minimum total cost
32 return totalCost;
33 }
34};
35
1function minimumCost(cost: number[]): number {
2 // Sort the array in ascending order to ensure that the minimum values are skipped
3 cost.sort((a, b) => a - b);
4
5 let totalCost = 0; // Initialize the total cost variable
6
7 // Start iterating from the end of array towards the front
8 // We jump three steps at a time since every third flower is free
9 for (let i = cost.length - 1; i >= 0; i -= 3) {
10 totalCost += cost[i]; // Add the cost of the current flower
11 // If there is another flower before the current one, add that cost too
12 if (i > 0) {
13 totalCost += cost[i - 1];
14 }
15 }
16
17 // Return the total cost after applying the discount for every third flower
18 return totalCost;
19}
20
Time and Space Complexity
The given Python code aims to calculate the minimum cost of purchasing items given that every third item is free when items are purchased in descending order of cost. We will analyze the time and space complexity of the provided code.
Time Complexity:
- The
sort
function on the listcost
has a time complexity ofO(n log n)
wheren
is the number of elements in the listcost
. This is because Python's Timsort algorithm is used for sorting, which has this time complexity. - The
sum
function iterates over the entire list, resulting inO(n)
time complexity. - The slicing operation
cost[2::3]
generates a new list containing every third element from the listcost
, leading toO(n/3)
complexity, which simplifies toO(n)
.
Combining these operations, the dominant term is the sorting operation. Thus, the total time complexity of the code is O(n log n)
.
Space Complexity:
- The sorting is done in-place, so there is no additional space required for this operation.
- The
sum
function operates on the list without creating new structures, so it usesO(1)
space. - The slicing operation
cost[2::3]
creates a new list, which in the worst case, could beO(n/3)
space complexity. However, since constants are dropped in Big O notation, it simplifies toO(n)
space complexity.
Therefore, the space complexity of the code is O(n)
, accounting for the space needed to store the new list generated by the slicing operation.
Learn more about how to find time and space complexity quickly using problem constraints.
Which of the following is a good use case for backtracking?
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
LeetCode 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 we
Want a Structured Path to Master System Design Too? Don’t Miss This!