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 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 feasible(min_diff: int) -> bool:
16            """
17            Check if we CANNOT select k candies with minimum difference >= min_diff.
18            This is the inverted condition for the first_true_index template.
19
20            Args:
21                min_diff: The minimum difference to maintain between selected candies
22
23            Returns:
24                True if k candies CANNOT be selected (infeasible)
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 True when infeasible (cannot select k candies)
36            return count < k
37
38        # Sort prices to enable greedy selection
39        price.sort()
40
41        # Binary search using first_true_index template
42        # We search for the first value where feasible returns True (infeasible)
43        # The answer is first_true_index - 1
44        left = 0
45        right = price[-1] - price[0] + 1  # +1 to include the boundary
46        first_true_index = -1
47
48        while left <= right:
49            mid = (left + right) // 2
50            if feasible(mid):
51                # Found an infeasible point, record it and search left
52                first_true_index = mid
53                right = mid - 1
54            else:
55                # Still feasible, search right for first infeasible
56                left = mid + 1
57
58        # The maximum feasible value is first_true_index - 1
59        return first_true_index - 1 if first_true_index > 0 else 0
60
1class Solution {
2    /**
3     * Finds the maximum minimum difference (tastiness) when selecting k items from the price array.
4     * Uses binary search with the first_true_index template pattern.
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 using first_true_index template
15        // We search for the first infeasible value, then subtract 1
16        int left = 0;
17        int right = price[price.length - 1] - price[0] + 1;
18        int firstTrueIndex = -1;
19
20        while (left <= right) {
21            int mid = left + (right - left) / 2;
22
23            // Check if we CANNOT select k items with minimum difference of at least mid
24            if (isInfeasible(price, k, mid)) {
25                // Found an infeasible point, record it and search left
26                firstTrueIndex = mid;
27                right = mid - 1;
28            } else {
29                // Still feasible, search right for first infeasible
30                left = mid + 1;
31            }
32        }
33
34        // The maximum feasible value is firstTrueIndex - 1
35        return firstTrueIndex > 0 ? firstTrueIndex - 1 : 0;
36    }
37
38    /**
39     * Checks if it's IMPOSSIBLE to select k items with minimum difference of at least minDifference.
40     * This is the inverted condition for the first_true_index template.
41     *
42     * @param price Sorted array of item prices
43     * @param k Number of items to select
44     * @param minDifference Minimum required difference between consecutive selected items
45     * @return true if k items CANNOT be selected (infeasible), false otherwise
46     */
47    private boolean isInfeasible(int[] price, int k, int minDifference) {
48        int selectedCount = 0;
49        int previousPrice = -minDifference;  // Initialize to ensure first item can be selected
50
51        // Greedily select items that maintain the minimum difference
52        for (int currentPrice : price) {
53            if (currentPrice - previousPrice >= minDifference) {
54                // Select this item as it satisfies the minimum difference constraint
55                previousPrice = currentPrice;
56                selectedCount++;
57            }
58        }
59
60        // Return true when infeasible (cannot select k items)
61        return selectedCount < k;
62    }
63}
64
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 using first_true_index template
8        // We search for the first infeasible value, then subtract 1
9        int left = 0;
10        int right = price.back() - price[0] + 1;
11        int firstTrueIndex = -1;
12
13        // Helper function to check if we CANNOT select k items with minimum difference of minDiff
14        auto isInfeasible = [&](int minDiff) -> bool {
15            int selectedCount = 0;
16            int previousSelected = -minDiff;  // Initialize to ensure first item can be selected
17
18            // Greedily select items that maintain at least minDiff distance
19            for (int& currentPrice : price) {
20                if (currentPrice - previousSelected >= minDiff) {
21                    previousSelected = currentPrice;
22                    ++selectedCount;
23                }
24            }
25
26            // Return true when infeasible (cannot select k items)
27            return selectedCount < k;
28        };
29
30        // Binary search for the first infeasible value
31        while (left <= right) {
32            int mid = left + (right - left) / 2;
33
34            if (isInfeasible(mid)) {
35                // Found an infeasible point, record it and search left
36                firstTrueIndex = mid;
37                right = mid - 1;
38            } else {
39                // Still feasible, search right for first infeasible
40                left = mid + 1;
41            }
42        }
43
44        // The maximum feasible value is firstTrueIndex - 1
45        return firstTrueIndex > 0 ? firstTrueIndex - 1 : 0;
46    }
47};
48
1/**
2 * Finds the maximum minimum difference between k selected prices
3 * Uses the first_true_index template pattern with inverted condition.
4 * @param price - Array of prices to choose from
5 * @param k - Number of prices to select
6 * @returns Maximum possible minimum difference between selected prices
7 */
8function maximumTastiness(price: number[], k: number): number {
9    // Sort prices in ascending order for binary search approach
10    price.sort((a, b) => a - b);
11
12    // Binary search using first_true_index template
13    // We search for the first infeasible value, then subtract 1
14    let left = 0;
15    let right = price[price.length - 1] - price[0] + 1;
16    let firstTrueIndex = -1;
17
18    /**
19     * Checks if it's IMPOSSIBLE to select k prices with minimum difference of at least minDifference
20     * This is the inverted condition for the first_true_index template.
21     * @param minDifference - The minimum difference to maintain between selected prices
22     * @returns True if k prices CANNOT be selected (infeasible)
23     */
24    const isInfeasible = (minDifference: number): boolean => {
25        // Initialize count and previous selected price
26        let selectedCount = 0;
27        let previousPrice = -minDifference; // Ensures first price can always be selected
28
29        // Greedily select prices that maintain minimum difference
30        for (const currentPrice of price) {
31            if (currentPrice - previousPrice >= minDifference) {
32                previousPrice = currentPrice;
33                selectedCount++;
34            }
35        }
36
37        // Return true when infeasible (cannot select k prices)
38        return selectedCount < k;
39    };
40
41    // Binary search for the first infeasible value
42    while (left <= right) {
43        const mid = Math.floor((left + right) / 2);
44
45        if (isInfeasible(mid)) {
46            // Found an infeasible point, record it and search left
47            firstTrueIndex = mid;
48            right = mid - 1;
49        } else {
50            // Still feasible, search right for first infeasible
51            left = mid + 1;
52        }
53    }
54
55    // The maximum feasible value is firstTrueIndex - 1
56    return firstTrueIndex > 0 ? firstTrueIndex - 1 : 0;
57}
58

Solution Approach

The solution uses binary search with the first_true_index template pattern combined with a greedy validation function.

Key Insight for Template Adaptation: This is a maximization problem where we want the largest feasible value. To use the first_true_index template, we invert the condition: instead of "can we achieve tastiness x?", we ask "is x infeasible?". Then we find the first infeasible value and subtract 1.

Step 1: Sort the prices

price.sort()

Sorting allows us to work with candies in order of increasing price.

Step 2: Set up binary search with the template

left = 0
right = price[-1] - price[0] + 1  # +1 to include the boundary
first_true_index = -1

while left <= right:
    mid = (left + right) // 2
    if feasible(mid):  # Returns True when infeasible
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1

return first_true_index - 1

Step 3: The inverted feasible function

def feasible(min_diff: int) -> bool:
    count = 0
    last_selected = -min_diff

    for current_price in price:
        if current_price - last_selected >= min_diff:
            last_selected = current_price
            count += 1

    # Return True when infeasible (cannot select k candies)
    return count < k

This greedy function returns True when we CANNOT select k candies with minimum difference of at least x. The pattern of values is: false, false, ..., true, true, ... where the first true marks the first infeasible value.

The algorithm works because:

  1. We find first_true_index: the smallest value where we cannot achieve the tastiness
  2. The maximum feasible tastiness is first_true_index - 1
  3. This maintains the canonical template structure while solving a maximization problem

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 5-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).

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. Using Wrong Binary Search Template Variant

A common mistake is using the while left < right pattern with ceiling division instead of the standard first_true_index template.

Why this causes problems:

  • Different binary search variants have subtle differences in boundary handling
  • Mixing patterns leads to off-by-one errors or infinite loops
  • The standard template provides consistency across all binary search problems

Solution: For maximization problems, use the first_true_index template with an inverted condition:

left, right = 0, max_value + 1
first_true_index = -1

while left <= right:
    mid = (left + right) // 2
    if not feasible(mid):  # Inverted: True when infeasible
        first_true_index = mid
        right = mid - 1
    else:
        left = mid + 1

return first_true_index - 1  # Maximum feasible value

This maintains the canonical template structure while solving maximization problems.

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.

Loading...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which algorithm should you use to find a node that is close to the root of the tree?


Recommended Readings

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

Load More