Facebook Pixel

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)
Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. If we can achieve a tastiness of x, we can also achieve any tastiness less than x (by selecting the same candies)
  2. If we cannot achieve a tastiness of x, we cannot achieve any tastiness greater than x

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) and pre = -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 increment cnt
  • Return True if we can select at least k candies, False otherwise

The algorithm works because:

  1. If we can achieve tastiness x, the check function returns True, so we try a larger value (l = mid)
  2. If we cannot achieve tastiness x, we need to try a smaller value (r = mid - 1)
  3. 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 Evaluator

Example 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
  • 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
  • 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
  • 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
  • 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
  • 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) where n is the length of the price list
  • Binary search runs in O(log(max_price - min_price)) iterations, where max_price - min_price represents the search space
  • For each binary search iteration, the check function is called which takes O(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 to O(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 typically O(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 and right are adjacent (e.g., left = 3, right = 4), using floor division gives mid = 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 of last_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 than min_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.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More