LeetCode 2819 - Minimum Relative Loss After Buying Chocolates
We are given an array prices, where each value represents the price of a chocolate. For every query [k, m], Bob wants to choose exactly m chocolates. The payment rule depends on the threshold k: - If a chocolate costs at most k, Bob pays the entire price.
Difficulty: 🔴 Hard
Topics: Array, Binary Search, Sorting, Prefix Sum
Solution
LeetCode 2819 - Minimum Relative Loss After Buying Chocolates
Problem Understanding
We are given an array prices, where each value represents the price of a chocolate. For every query [k, m], Bob wants to choose exactly m chocolates.
The payment rule depends on the threshold k:
- If a chocolate costs at most
k, Bob pays the entire price. - If a chocolate costs more than
k, Bob pays exactlyk, and Alice pays the remaining amount.
Suppose Bob chooses a set of chocolates. Let:
b= total amount paid by Boba= total amount paid by Alice
Bob's relative loss is defined as:
$$b - a$$
For each query, we must find the minimum possible value of b - a among all ways to choose exactly m chocolates.
The array length and the number of queries are both up to 10^5, so any solution that examines all subsets or even all possible groups of size m is completely infeasible. We need an algorithm that can answer each query in roughly logarithmic time after preprocessing.
A few observations are immediately important.
For a chocolate with price p:
- If
p ≤ k, then Bob payspand Alice pays0, contributing
$$p$$
to b - a.
- If
p > k, then Bob payskand Alice paysp-k, contributing
$$k-(p-k)=2k-p$$
to b-a.
Therefore every chocolate independently contributes:
$$f(p)= \begin{cases} p,& p\le k\ 2k-p,& p>k \end{cases}$$
For a fixed query, the problem becomes:
Choose exactly
mchocolates whose contribution values sum to the minimum possible total.
This reformulation is the key to the entire solution.
Important edge cases include:
ksmaller than every chocolate price.klarger than every chocolate price.m = 1.m = n.- Multiple equal prices.
- Prices as large as
10^9, requiring 64-bit arithmetic.
The problem guarantees:
1 ≤ n ≤ 10^51 ≤ q ≤ 10^5- All prices are positive.
- Every query requests a valid number of chocolates.
Approaches
Brute Force
For a fixed query, we can compute the contribution of every chocolate and then choose the smallest m contributions.
This is correct because the objective is simply the sum of chosen contribution values.
However, doing this independently for every query requires:
- Computing
ncontributions. - Sorting them.
- Summing the smallest
m.
That costs O(n log n) per query.
With both n and q up to 10^5, this becomes far too slow.
Key Insight
After sorting the prices, the contribution function has a very special structure.
For a query threshold k:
$$f(p)=p \quad (p\le k)$$
and
$$f(p)=2k-p \quad (p>k)$$
Notice:
- For prices
≤ k, contributions increase with price. - For prices
> k, contributions decrease with price.
Therefore:
- Among chocolates priced above
k, the cheapest contributions come from the largest prices. - Among chocolates priced at most
k, the cheapest contributions come from the smallest prices.
After sorting the prices:
- The left side (
≤ k) contributes candidates from the beginning. - The right side (
> k) contributes candidates from the end.
The optimal set of m chocolates must therefore consist of:
- some number
xtaken from the left prefix, - and
m-xtaken from the right suffix.
No middle elements are ever needed.
This reduces the problem to finding the correct value of x.
Even better, we can derive a monotonic condition and use binary search.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(q · n log n) | O(n) | Recompute contributions for every query |
| Optimal | O(n log n + q log n) | O(n) | Sort once, answer each query with binary search |
Algorithm Walkthrough
Assume the prices are sorted.
Let:
pos = upper_bound(prices, k)cnt = pos
Then:
- indices
[0, cnt-1]contain prices≤ k - indices
[cnt, n-1]contain prices> k
Let x be the number chosen from the left part.
Then:
m-xare chosen from the right part.
Step 1
Sort the prices and build prefix sums.
Let:
$$prefix[i]$$
store the sum of the first i sorted prices.
This allows constant-time range-sum queries.
Step 2
For a query (k,m), find
$$cnt = #{p \le k}$$
using binary search.
Step 3
Observe the optimal candidates.
If we choose x chocolates from the left side, the minimum contribution from that side comes from the smallest x prices.
Their contribution sum is:
$$L(x)=prefix[x]$$
If we choose m-x chocolates from the right side, the minimum contribution comes from the largest m-x prices.
Let
$$S$$
be the sum of those largest prices.
Their contribution becomes:
$$(m-x)\cdot 2k - S$$
Step 4
Determine the valid range of x.
We cannot choose more than cnt chocolates from the left side.
We also cannot choose more than n-cnt chocolates from the right side.
Therefore:
$$\max(0,m-(n-cnt)) \le x \le \min(m,cnt)$$
Step 5
Derive the optimal boundary.
Increasing x by one means:
- removing one chosen right-side chocolate,
- adding one left-side chocolate.
The objective changes monotonically.
After algebra, the optimal x becomes the largest valid value satisfying:
$$prices[x-1]+prices[n-(m-x)] \le 2k$$
This condition is monotonic in x, so binary search can find the best value.
Step 6
Compute the answer for that optimal x.
The left contribution is:
$$prefix[x]$$
The right contribution uses the largest m-x prices:
$$S = prefix[n]-prefix[n-(m-x)]$$
Thus:
$$answer = prefix[x] + (m-x)\cdot 2k - S$$
Why it Works
The contribution values form two monotonic groups:
- increasing among prices
≤ k, - decreasing among prices
> k.
Therefore the globally smallest contribution values must come from the smallest prices on the left and the largest prices on the right.
The choice is completely determined by how many items are taken from each side. The marginal effect of increasing x is monotonic, producing a binary-searchable boundary. Once the optimal x is found, the corresponding contribution sum is exactly the minimum relative loss.
Python Solution
from bisect import bisect_right
from typing import List
class Solution:
def minimumRelativeLosses(self, prices: List[int], queries: List[List[int]]) -> List[int]:
prices.sort()
n = len(prices)
prefix = [0] * (n + 1)
for i, p in enumerate(prices):
prefix[i + 1] = prefix[i] + p
answers = []
for k, m in queries:
cnt = bisect_right(prices, k)
low = max(0, m - (n - cnt))
high = min(m, cnt)
l, r = low, high
while l < r:
mid = (l + r + 1) // 2
left_price = prices[mid - 1]
right_price = prices[n - (m - mid)]
if left_price + right_price <= 2 * k:
l = mid
else:
r = mid - 1
x = l
left_sum = prefix[x]
take_right = m - x
right_sum = prefix[n] - prefix[n - take_right]
answer = left_sum + take_right * (2 * k) - right_sum
answers.append(answer)
return answers
Implementation Explanation
The first step sorts the prices and constructs a prefix-sum array. The prefix sums allow us to obtain the sum of any prefix or suffix in constant time.
For each query, bisect_right finds how many prices are at most k. This divides the sorted array into the two regions required by the contribution function.
The variable x represents how many chocolates are chosen from the left region. The feasible interval for x is computed from the available counts on both sides.
A binary search then finds the largest valid x satisfying the optimality condition
$$prices[x-1] + prices[n-(m-x)] \le 2k.$$
Once x is known, the selected left chocolates are exactly the first x prices, and the selected right chocolates are exactly the largest m-x prices. Prefix sums provide both totals immediately.
The final formula directly computes Bob's minimum relative loss.
Go Solution
package main
import "sort"
func minimumRelativeLosses(prices []int, queries [][]int) []int64 {
sort.Ints(prices)
n := len(prices)
prefix := make([]int64, n+1)
for i, p := range prices {
prefix[i+1] = prefix[i] + int64(p)
}
ans := make([]int64, len(queries))
for qi, q := range queries {
k := q[0]
m := q[1]
cnt := sort.Search(n, func(i int) bool {
return prices[i] > k
})
low := max(0, m-(n-cnt))
high := min(m, cnt)
l, r := low, high
for l < r {
mid := (l + r + 1) / 2
leftPrice := prices[mid-1]
rightPrice := prices[n-(m-mid)]
if leftPrice+rightPrice <= 2*k {
l = mid
} else {
r = mid - 1
}
}
x := l
leftSum := prefix[x]
rightCount := m - x
rightSum := prefix[n] - prefix[n-rightCount]
ans[qi] = leftSum + int64(rightCount)*int64(2*k) - rightSum
}
return ans
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
Go-Specific Notes
The logic is identical to the Python solution. The main difference is that all arithmetic involving sums uses int64, because the total price of selected chocolates can exceed the range of a 32-bit integer.
Go's sort.Search is used both for finding the partition point and for implementing binary search efficiently.
Worked Examples
Example 1
Input:
prices = [1,9,22,10,19]
query = [18,4]
Sorted prices:
[1,9,10,19,22]
Prefix sums:
| i | prefix[i] |
|---|---|
| 0 | 0 |
| 1 | 1 |
| 2 | 10 |
| 3 | 20 |
| 4 | 39 |
| 5 | 61 |
cnt = 3 because [1,9,10] ≤ 18.
Valid range:
low=max(0,4-2)=2
high=min(4,3)=3
Binary search:
| x | Condition |
|---|---|
| 3 | 10 + 19 = 29 ≤ 36 |
Optimal:
x = 3
Compute:
left_sum = 20
take_right = 1
right_sum = 22
Answer:
20 + 36 - 22 = 34
Example 2, Query [5,4]
Sorted:
[1,3,4,5,7,9,11]
cnt = 4
Valid range:
1 ≤ x ≤ 4
Binary search yields:
x = 2
Compute:
left_sum = 1 + 3 = 4
right_sum = 7 + 9 + 11 = 27
Answer:
4 + 3*10 - 27
= 4
Example 2, Query [5,7]
All chocolates must be selected.
x = 4
Compute:
left_sum = 13
right_sum = 27
Answer:
13 + 3*10 - 27 = 16
Example 3
Input:
prices = [5,6,7]
query = [3,3]
All prices exceed k.
cnt = 0
x = 0
Right sum:
5 + 6 + 7 = 18
Answer:
3*6 - 18 = 0
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n log n + q log n) | Sort once, binary search per query |
| Space | O(n) | Prefix-sum array |
The dominant preprocessing cost is sorting the prices. After that, each query performs two binary searches and constant-time prefix-sum calculations, resulting in O(log n) per query. With up to 10^5 queries, this comfortably fits within the constraints.
Test Cases
sol = Solution()
assert sol.minimumRelativeLosses(
[1,9,22,10,19],
[[18,4],[5,2]]
) == [34, -21] # example 1
assert sol.minimumRelativeLosses(
[1,5,4,3,7,11,9],
[[5,4],[5,7],[7,3],[4,5]]
) == [4, 16, 7, 1] # example 2
assert sol.minimumRelativeLosses(
[5,6,7],
[[10,1],[5,3],[3,3]]
) == [5, 12, 0] # example 3
assert sol.minimumRelativeLosses(
[10],
[[5,1]]
) == [0] # single chocolate above k
assert sol.minimumRelativeLosses(
[3],
[[10,1]]
) == [3] # single chocolate below k
assert sol.minimumRelativeLosses(
[5,5,5,5],
[[5,2]]
) == [10] # duplicate values
assert sol.minimumRelativeLosses(
[1,2,3,4,5],
[[100,3]]
) == [6] # all prices <= k
assert sol.minimumRelativeLosses(
[1,2,3,4,5],
[[1,5]]
) == [-3] # all chocolates selected
assert sol.minimumRelativeLosses(
[10**9,10**9,10**9],
[[10**9,3]]
) == [3000000000] # large values
Test Summary
| Test | Why |
|---|---|
| Example 1 | Verifies mixed selection from both sides |
| Example 2 | Verifies multiple query types |
| Example 3 | Verifies all prices greater than k |
Single chocolate above k |
Smallest possible input |
Single chocolate below k |
Boundary case |
| Duplicate prices | Ensures equal values work correctly |
All prices ≤ k |
Entire array on left side |
| All chocolates selected | Tests m = n |
| Large values | Verifies 64-bit arithmetic |
Edge Cases
All Prices Are Greater Than k
When every chocolate price exceeds k, we have cnt = 0. No chocolates can be chosen from the left side, so x = 0 is forced. The algorithm naturally handles this because the valid range collapses to a single value. The answer becomes the contribution sum of the largest m prices under the formula 2k-p.
All Prices Are Less Than or Equal to k
In this situation, every contribution equals the original price. The optimal strategy is simply selecting the m smallest prices. Since cnt = n, the binary search ultimately chooses the maximum feasible x, causing the formula to reduce exactly to the sum of the smallest m prices.
Selecting Every Chocolate
When m = n, there is no choice of subset. A common source of bugs is incorrect suffix indexing because the number of right-side selections becomes completely determined by the partition. The implementation carefully computes the valid interval for x, ensuring all indices remain valid and the final formula still works.
Large Price Values
Prices can reach 10^9, and up to 10^5 chocolates may be selected. The resulting sums can be around 10^14. Using 32-bit integers would overflow. The Python implementation is naturally safe because Python integers are arbitrary precision, while the Go implementation explicitly uses int64 for all prefix sums and answer calculations.