2517. Maximum Tastiness of Candy Basket
Problem Description
You have an array price
containing positive integers, where price[i]
represents the price of the i-th candy. You also have a positive integer k
.
The store sells baskets containing exactly k
distinct candies. The tastiness of a candy basket is defined as the smallest absolute difference between the prices of any two candies in that basket.
Your task is to find the maximum possible tastiness value when selecting k
distinct candies from the available candies.
Example to understand tastiness:
- If you have a basket with candies priced at [1, 5, 9], the absolute differences between pairs are:
- |5 - 1| = 4
- |9 - 5| = 4
- |9 - 1| = 8
- The tastiness would be min(4, 4, 8) = 4
The goal is to select k
candies such that the minimum pairwise price difference is as large as possible.
Key Points:
- You must select exactly
k
distinct candies - The tastiness is the minimum difference between any two selected candies
- You want to maximize this tastiness value
- The candies are distinct (you can't pick the same candy twice)
Intuition
When we need to maximize the minimum difference between selected candies, we want to spread out our selections as much as possible. Think of it like placing k
points on a number line - to maximize the minimum distance between any two points, we'd want to space them out evenly.
The key insight is that if we sort the prices first, we can transform this into a different problem: instead of finding which candies to select, we can ask "given a minimum difference x
, can we select k
candies such that every pair has at least x
difference?"
This reframing leads us to binary search on the answer. Why? Because:
- If we can achieve a tastiness of
x
, we can also achieve any tastiness less thanx
(by selecting the same candies) - If we cannot achieve a tastiness of
x
, we cannot achieve any tastiness greater thanx
This monotonic property makes binary search perfect for this problem.
The search space for our answer is between:
- Minimum:
0
(worst case, though we could have identical prices) - Maximum:
price[-1] - price[0]
(the difference between the most and least expensive candy after sorting)
For each candidate tastiness value mid
, we greedily check if we can select k
candies with minimum difference of at least mid
. The greedy strategy works because once we sort the prices, we can always start with the first candy and keep selecting the next candy that is at least mid
distance away. If we can find k
such candies, then mid
is achievable; otherwise, we need to try a smaller value.
The greedy selection is optimal because selecting candies that are closer together than necessary doesn't help us - we want to use our "budget" of candies efficiently by spreading them out as much as the current mid
value requires.
Learn more about Greedy, Binary Search and Sorting patterns.
Solution Approach
The solution uses binary search combined with a greedy validation function. Here's how it works:
Step 1: Sort the prices
price.sort()
Sorting allows us to work with candies in order of increasing price, which makes the greedy selection strategy work.
Step 2: Set up binary search boundaries
l, r = 0, price[-1] - price[0]
- Left boundary
l = 0
: minimum possible tastiness - Right boundary
r = price[-1] - price[0]
: maximum possible tastiness (difference between most and least expensive candy)
Step 3: Binary search for the maximum valid tastiness
while l < r: mid = (l + r + 1) >> 1 if check(mid): l = mid else: r = mid - 1
We use binary search to find the largest tastiness value that allows selecting k
candies. The (l + r + 1) >> 1
is equivalent to (l + r + 1) // 2
, which calculates the ceiling of the midpoint to avoid infinite loops when l
and r
are adjacent.
Step 4: The validation function check(x)
def check(x: int) -> bool:
cnt, pre = 0, -x
for cur in price:
if cur - pre >= x:
pre = cur
cnt += 1
return cnt >= k
This greedy function checks if we can select k
candies with minimum difference of at least x
:
- Initialize
cnt = 0
(count of selected candies) andpre = -x
(previous selected candy price, initialized to ensure first candy is always selected) - Iterate through sorted prices
- Select a candy if its price difference from the previously selected candy is at least
x
- Update
pre
to current candy's price and incrementcnt
- Return
True
if we can select at leastk
candies,False
otherwise
The algorithm works because:
- If we can achieve tastiness
x
, thecheck
function returnsTrue
, so we try a larger value (l = mid
) - If we cannot achieve tastiness
x
, we need to try a smaller value (r = mid - 1
) - The loop continues until
l == r
, which gives us the maximum achievable tastiness
Time Complexity: O(n log(max_price - min_price))
where n
is the length of the price array
Space Complexity: O(1)
excluding the sorting space
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 walk through an example with price = [13, 5, 1, 8, 21, 2]
and k = 3
.
Step 1: Sort the prices
After sorting: price = [1, 2, 5, 8, 13, 21]
Step 2: Initialize binary search boundaries
l = 0
(minimum possible tastiness)r = 21 - 1 = 20
(maximum possible tastiness)
Step 3: Binary search iterations
Iteration 1:
mid = (0 + 20 + 1) >> 1 = 10
- Check if we can achieve tastiness of 10:
- Start with
pre = -10
,cnt = 0
- Candy at price 1:
1 - (-10) = 11 >= 10
✓ Select it.pre = 1
,cnt = 1
- Candy at price 2:
2 - 1 = 1 < 10
✗ Skip - Candy at price 5:
5 - 1 = 4 < 10
✗ Skip - Candy at price 8:
8 - 1 = 7 < 10
✗ Skip - Candy at price 13:
13 - 1 = 12 >= 10
✓ Select it.pre = 13
,cnt = 2
- Candy at price 21:
21 - 13 = 8 < 10
✗ Skip - Result:
cnt = 2 < 3
→ Cannot achieve tastiness of 10
- Start with
- Update:
r = 10 - 1 = 9
Iteration 2:
mid = (0 + 9 + 1) >> 1 = 5
- Check if we can achieve tastiness of 5:
- Start with
pre = -5
,cnt = 0
- Candy at price 1:
1 - (-5) = 6 >= 5
✓ Select it.pre = 1
,cnt = 1
- Candy at price 2:
2 - 1 = 1 < 5
✗ Skip - Candy at price 5:
5 - 1 = 4 < 5
✗ Skip - Candy at price 8:
8 - 1 = 7 >= 5
✓ Select it.pre = 8
,cnt = 2
- Candy at price 13:
13 - 8 = 5 >= 5
✓ Select it.pre = 13
,cnt = 3
- Candy at price 21: We already have 3 candies
- Result:
cnt = 3 >= 3
→ Can achieve tastiness of 5
- Start with
- Update:
l = 5
Iteration 3:
mid = (5 + 9 + 1) >> 1 = 7
- Check if we can achieve tastiness of 7:
- Start with
pre = -7
,cnt = 0
- Candy at price 1:
1 - (-7) = 8 >= 7
✓ Select it.pre = 1
,cnt = 1
- Candy at price 2:
2 - 1 = 1 < 7
✗ Skip - Candy at price 5:
5 - 1 = 4 < 7
✗ Skip - Candy at price 8:
8 - 1 = 7 >= 7
✓ Select it.pre = 8
,cnt = 2
- Candy at price 13:
13 - 8 = 5 < 7
✗ Skip - Candy at price 21:
21 - 8 = 13 >= 7
✓ Select it.pre = 21
,cnt = 3
- Result:
cnt = 3 >= 3
→ Can achieve tastiness of 7
- Start with
- Update:
l = 7
Iteration 4:
mid = (7 + 9 + 1) >> 1 = 8
- Check if we can achieve tastiness of 8:
- Start with
pre = -8
,cnt = 0
- Candy at price 1:
1 - (-8) = 9 >= 8
✓ Select it.pre = 1
,cnt = 1
- Candy at price 2:
2 - 1 = 1 < 8
✗ Skip - Candy at price 5:
5 - 1 = 4 < 8
✗ Skip - Candy at price 8:
8 - 1 = 7 < 8
✗ Skip - Candy at price 13:
13 - 1 = 12 >= 8
✓ Select it.pre = 13
,cnt = 2
- Candy at price 21:
21 - 13 = 8 >= 8
✓ Select it.pre = 21
,cnt = 3
- Result:
cnt = 3 >= 3
→ Can achieve tastiness of 8
- Start with
- Update:
l = 8
Iteration 5:
mid = (8 + 9 + 1) >> 1 = 9
- Check if we can achieve tastiness of 9:
- Start with
pre = -9
,cnt = 0
- Candy at price 1:
1 - (-9) = 10 >= 9
✓ Select it.pre = 1
,cnt = 1
- Candy at price 2:
2 - 1 = 1 < 9
✗ Skip - Candy at price 5:
5 - 1 = 4 < 9
✗ Skip - Candy at price 8:
8 - 1 = 7 < 9
✗ Skip - Candy at price 13:
13 - 1 = 12 >= 9
✓ Select it.pre = 13
,cnt = 2
- Candy at price 21:
21 - 13 = 8 < 9
✗ Skip - Result:
cnt = 2 < 3
→ Cannot achieve tastiness of 9
- Start with
- Update:
r = 9 - 1 = 8
Final result: l = r = 8
The maximum tastiness is 8, achieved by selecting candies with prices [1, 13, 21]. The minimum difference between any pair is indeed 8 (21 - 13 = 8).
Solution Implementation
1class Solution:
2 def maximumTastiness(self, price: List[int], k: int) -> int:
3 """
4 Find the maximum minimum difference between any two chosen prices.
5 Uses binary search on the answer with a greedy validation function.
6
7 Args:
8 price: List of candy prices
9 k: Number of candies to select
10
11 Returns:
12 Maximum possible minimum difference between selected candies
13 """
14
15 def can_achieve_min_diff(min_diff: int) -> bool:
16 """
17 Check if we can select k candies with minimum difference >= min_diff.
18 Uses greedy approach: always pick the next available candy.
19
20 Args:
21 min_diff: The minimum difference to maintain between selected candies
22
23 Returns:
24 True if k candies can be selected with the given minimum difference
25 """
26 count = 0
27 last_selected = -min_diff # Initialize to ensure first candy can always be selected
28
29 for current_price in price:
30 # Select current candy if it's far enough from the last selected one
31 if current_price - last_selected >= min_diff:
32 last_selected = current_price
33 count += 1
34
35 return count >= k
36
37 # Sort prices to enable greedy selection
38 price.sort()
39
40 # Binary search bounds: minimum diff is 0, maximum is range of prices
41 left = 0
42 right = price[-1] - price[0]
43
44 # Binary search for the maximum valid minimum difference
45 while left < right:
46 # Use ceiling division to avoid infinite loop
47 mid = (left + right + 1) // 2
48
49 if can_achieve_min_diff(mid):
50 # If this difference works, try for a larger one
51 left = mid
52 else:
53 # If this difference doesn't work, try smaller
54 right = mid - 1
55
56 return left
57
1class Solution {
2 /**
3 * Finds the maximum minimum difference (tastiness) when selecting k items from the price array.
4 * Uses binary search to find the optimal minimum difference between selected items.
5 *
6 * @param price Array of item prices
7 * @param k Number of items to select
8 * @return Maximum possible minimum difference between any two selected items
9 */
10 public int maximumTastiness(int[] price, int k) {
11 // Sort the price array to enable binary search on differences
12 Arrays.sort(price);
13
14 // Binary search on the answer: minimum difference can be from 0 to max-min
15 int left = 0;
16 int right = price[price.length - 1] - price[0];
17
18 while (left < right) {
19 // Calculate mid point (using +1 to avoid infinite loop when left = right - 1)
20 int mid = (left + right + 1) >> 1;
21
22 // Check if we can select k items with minimum difference of at least mid
23 if (canSelectKItemsWithMinDifference(price, k, mid)) {
24 // If possible, try for a larger minimum difference
25 left = mid;
26 } else {
27 // If not possible, try for a smaller minimum difference
28 right = mid - 1;
29 }
30 }
31
32 return left;
33 }
34
35 /**
36 * Checks if it's possible to select k items with minimum difference of at least minDifference.
37 * Uses a greedy approach: always select the next available item that satisfies the minimum difference.
38 *
39 * @param price Sorted array of item prices
40 * @param k Number of items to select
41 * @param minDifference Minimum required difference between consecutive selected items
42 * @return true if k items can be selected with the given minimum difference, false otherwise
43 */
44 private boolean canSelectKItemsWithMinDifference(int[] price, int k, int minDifference) {
45 int selectedCount = 0;
46 int previousPrice = -minDifference; // Initialize to ensure first item can be selected
47
48 // Greedily select items that maintain the minimum difference
49 for (int currentPrice : price) {
50 if (currentPrice - previousPrice >= minDifference) {
51 // Select this item as it satisfies the minimum difference constraint
52 previousPrice = currentPrice;
53 selectedCount++;
54 }
55 }
56
57 // Check if we were able to select at least k items
58 return selectedCount >= k;
59 }
60}
61
1class Solution {
2public:
3 int maximumTastiness(vector<int>& price, int k) {
4 // Sort prices in ascending order for binary search approach
5 sort(price.begin(), price.end());
6
7 // Binary search range: minimum difference is 0, maximum is the range of prices
8 int left = 0;
9 int right = price.back() - price[0];
10
11 // Helper function to check if we can select k items with minimum difference of minDiff
12 auto canSelectWithMinDifference = [&](int minDiff) -> bool {
13 int selectedCount = 0;
14 int previousSelected = -minDiff; // Initialize to ensure first item can be selected
15
16 // Greedily select items that maintain at least minDiff distance
17 for (int& currentPrice : price) {
18 if (currentPrice - previousSelected >= minDiff) {
19 previousSelected = currentPrice;
20 ++selectedCount;
21 }
22 }
23
24 // Check if we can select at least k items
25 return selectedCount >= k;
26 };
27
28 // Binary search for the maximum minimum difference
29 while (left < right) {
30 // Use upper mid to avoid infinite loop when left and right are adjacent
31 int mid = (left + right + 1) >> 1;
32
33 if (canSelectWithMinDifference(mid)) {
34 // If we can achieve this difference, try for a larger one
35 left = mid;
36 } else {
37 // If we cannot achieve this difference, try for a smaller one
38 right = mid - 1;
39 }
40 }
41
42 return left;
43 }
44};
45
1/**
2 * Finds the maximum minimum difference between k selected prices
3 * @param price - Array of prices to choose from
4 * @param k - Number of prices to select
5 * @returns Maximum possible minimum difference between selected prices
6 */
7function maximumTastiness(price: number[], k: number): number {
8 // Sort prices in ascending order for binary search approach
9 price.sort((a, b) => a - b);
10
11 // Binary search bounds: minimum and maximum possible tastiness
12 let left = 0;
13 let right = price[price.length - 1] - price[0];
14
15 /**
16 * Checks if it's possible to select k prices with minimum difference of at least minDifference
17 * @param minDifference - The minimum difference to maintain between selected prices
18 * @returns True if k prices can be selected with the given minimum difference
19 */
20 const canSelectWithMinDifference = (minDifference: number): boolean => {
21 // Initialize count and previous selected price
22 let selectedCount = 0;
23 let previousPrice = -minDifference; // Ensures first price can always be selected
24
25 // Greedily select prices that maintain minimum difference
26 for (const currentPrice of price) {
27 if (currentPrice - previousPrice >= minDifference) {
28 previousPrice = currentPrice;
29 selectedCount++;
30 }
31 }
32
33 // Check if we can select at least k prices
34 return selectedCount >= k;
35 };
36
37 // Binary search for the maximum valid minimum difference
38 while (left < right) {
39 // Calculate mid point (ceiling division to avoid infinite loop)
40 const mid = (left + right + 1) >> 1;
41
42 if (canSelectWithMinDifference(mid)) {
43 // If valid, try to find a larger minimum difference
44 left = mid;
45 } else {
46 // If not valid, reduce the minimum difference requirement
47 right = mid - 1;
48 }
49 }
50
51 return left;
52}
53
Time and Space Complexity
Time Complexity: O(n log n + n log(max_price))
The time complexity consists of two main parts:
- Sorting the price array takes
O(n log n)
wheren
is the length of the price list - Binary search runs in
O(log(max_price - min_price))
iterations, wheremax_price - min_price
represents the search space - For each binary search iteration, the
check
function is called which takesO(n)
time as it iterates through all prices once - Therefore, the binary search portion contributes
O(n * log(max_price - min_price))
- Since
max_price - min_price ≤ max_price
, we can simplify this toO(n log(max_price))
- Overall:
O(n log n + n log(max_price))
Space Complexity: O(1)
or O(log n)
- The algorithm uses only a constant amount of extra space for variables (
l
,r
,mid
,cnt
,pre
,cur
) - The sorting operation might use
O(log n)
space if an in-place sorting algorithm like quicksort is used (for recursion stack) - If the sorting is done using Timsort (Python's default), it would use
O(n)
auxiliary space in the worst case, but typicallyO(log n)
- Excluding the sorting space, the algorithm itself uses
O(1)
extra space
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Binary Search Midpoint Calculation
One of the most common mistakes in this problem is using mid = (left + right) // 2
instead of mid = (left + right + 1) // 2
when updating left = mid
.
Why this causes problems:
- When
left
andright
are adjacent (e.g.,left = 3, right = 4
), using floor division givesmid = 3
- If the check passes, we set
left = mid = 3
, creating an infinite loop - The ceiling division ensures
mid = 4
, allowing the search to progress
Solution:
Always use mid = (left + right + 1) // 2
when the update condition is left = mid
. Alternatively, use the template:
while left < right: mid = (left + right) // 2 if can_achieve_min_diff(mid + 1): # Check mid + 1 instead left = mid + 1 else: right = mid
2. Incorrect Greedy Validation Logic
Another pitfall is incorrectly implementing the validation function, particularly:
- Starting with
last_selected = 0
instead oflast_selected = -min_diff
- Using
<=
instead of>=
in the comparison - Forgetting to update
last_selected
after selecting a candy
Why this matters:
- Starting with
last_selected = 0
might incorrectly reject the first candy if its price is less thanmin_diff
- The greedy approach only works because we're iterating through sorted prices
Solution:
Initialize last_selected = -min_diff
or use a flag for the first candy:
def can_achieve_min_diff(min_diff):
count = 1 # Start with first candy selected
last_selected = price[0]
for i in range(1, len(price)):
if price[i] - last_selected >= min_diff:
last_selected = price[i]
count += 1
return count >= k
3. Forgetting to Sort the Array
The greedy approach only works on a sorted array. Without sorting, you cannot guarantee that selecting the next valid candy maintains the minimum difference constraint for all previously selected candies.
Solution: Always sort the price array before applying the binary search and greedy validation.
Problem: Given a list of tasks and a list of requirements, compute a sequence of tasks that can be performed, such that we complete every task once while satisfying all the requirements.
Which of the following method should we use to solve this problem?
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
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
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!