Greedy · 10 of 35
Find the minimum and maximum amount to buy all N candies
With a buy one, get K free deal, find the least and the most you could pay to take home every candy in the shop.
- Simple way
- time O(n^2)
room O(n) - Clever way
- time O(n log n)
room O(n)
The problem, simply
A candy shop has n different candies with prices prices[i]. The shop has a deal: every time you pay for one candy, you may take up to K other candies for free. Any candies, any price.
You must end up with every candy. Give back two numbers: the least money you could spend, and the most money you could spend.
A story to remember it
Picture it
Seven candies with their prices, as they sit on the shelf. The deal is buy one, get 2 free.
- K
- 2
Examples
A normal case
- In
prices = [3, 2, 1, 4], K = 2- Out
min 3, max 7
Least: pay 1, take 4 and 3 free, then pay 2. Most: pay 4, take 1 and 2 free, then pay 3.
A bigger shop
- In
prices = [3, 2, 1, 4, 5, 8, 6], K = 2- Out
min 6, max 19
You always pay for 3 candies. Cheapest three: 1 + 2 + 3. Priciest three: 8 + 6 + 5.
A huge deal
- In
prices = [3, 2, 1, 4, 5], K = 4- Out
min 1, max 5
One paid candy brings 4 free ones, which is everything else.
No deal
- In
prices = [2, 9, 4], K = 0- Out
min 15, max 15
Nothing is free, so you pay for all of them either way.
One candy
- In
prices = [7], K = 3- Out
min 7, max 7
You must pay for it. There is nothing left to take for free.
Way 1: the simple way
Act it out with a list of the candies still on the shelf, without sorting.
- For the least: while the shelf is not empty, find the cheapest candy, pay for it and remove it. Then remove the most expensive candy
Ktimes, for free. - For the most: same, but pay for the most expensive and remove the cheapest
Ktimes.
class Solution {
public long[] candyStore(int[] prices, int K) {
return new long[]{ act(prices, K, true), act(prices, K, false) };
}
private long act(int[] prices, int K, boolean cheap) {
List<Integer> shelf = new ArrayList<>();
for (int p : prices) shelf.add(p);
long paid = 0;
while (!shelf.isEmpty()) {
paid += shelf.remove(pick(shelf, cheap)); // pay for this one
for (int k = 0; k < K && !shelf.isEmpty(); k++) {
shelf.remove(pick(shelf, !cheap)); // take this one free
}
}
return paid;
}
// index of the cheapest candy (cheap = true) or the most expensive one
private int pick(List<Integer> shelf, boolean cheap) {
int best = 0;
for (int i = 1; i < shelf.size(); i++) {
boolean better = cheap ? shelf.get(i) < shelf.get(best) : shelf.get(i) > shelf.get(best);
if (better) best = i;
}
return best;
}
}How long it takes: every candy leaves the shelf once, and finding it scans the shelf, so O(n^2). Extra room: the shelf list, O(n).
Way 2: the clever way
Sort the prices once and use two fingers, one at each end.
- Sort the prices from cheap to expensive.
- Least:
buystarts at the cheap end,freeat the expensive end. Payprices[buy], movebuyone step right, movefreeKsteps left. Stop when they cross. - Most: the mirror image. Pay from the expensive end and take free candies from the cheap end.
A neat check: either way you pay for exactly ceil(n / (K + 1)) candies. The least is the sum of that many cheapest prices, and the most is the sum of that many priciest ones.
Two fingers on the sorted prices
01 / 09
Sort the prices. For the cheapest total, pay from the cheap end and take free candies from the expensive end. Each paid candy brings 2 free ones.
- min paid
- 0
- looking now
- done
- answer
- ignored
class Solution {
public long[] candyStore(int[] prices, int K) {
int[] p = prices.clone();
Arrays.sort(p);
int n = p.length;
long min = 0;
for (int buy = 0, free = n - 1; buy <= free; buy++, free -= K) {
min += p[buy]; // pay the cheapest left, K priciest go free
}
long max = 0;
for (int buy = n - 1, free = 0; free <= buy; buy--, free += K) {
max += p[buy]; // pay the priciest left, K cheapest go free
}
return new long[]{min, max};
}
}How long it takes: the sort is O(n log n), the two walks are O(n). Extra room: the sorted copy, O(n).
Watch out for
- When
freepassesbuyin the middle of a jump, stop. The loop conditionbuy <= freehandles it. - Sums can pass
intfor big shops. Uselong. - Some versions add a rule that the free candy must cost no more than the paid one. That is a different problem; read the statement.
K = 0means no deal. Both answers are the full sum.
What interviewers ask next
- Answer many queries with different
K? Sort once and build prefix sums. For eachK, the number paid isc = ceil(n / (K + 1)), the least is the sum of the firstcprices and the most is the sum of the lastc. Each query isO(1). - Why is greedy safe here? You must pay for at least
ceil(n / (K + 1))candies, and the greedy pays for exactly that many, the cheapest possible ones. Nothing can do better. - The "buy two, get a third that costs no more than either of them free" variant (LeetCode 2144)? Sort from priciest to cheapest and skip every third candy.
Remember this
Grown-up words: the two fingers are two pointers, the deal sets how many items are paid for, and the prefix sum trick answers many K values fast.