Question: Max purchases from a sorted price row
Companies: Uber
Difficulty: Medium
Problem statement
A market hall has n stalls in a row, numbered 1..n. Stall i sells a single kind of item at price prices[i-1], and the prices are non-decreasing from left to right. A shopper may buy at most one item per stall.
You are given q queries as two arrays of length q, pos and amount. For query j, the shopper starts at stall pos[j] and may visit only stalls pos[j], pos[j]+1, ..., n. Return an array where entry j is the maximum number of items the shopper can buy in query j with total cost at most amount[j].
Implement:
def max_items(prices: list[int], pos: list[int], amount: list[int]) -> list[int]: ...
Constraints
1 <= n, q <= 10^5-
0 <= prices[i] <= 10^9, andprices[i] <= prices[i+1] 1 <= pos[j] <= n0 <= amount[j] <= 10^14
Example 1
prices = [2, 3, 5, 8]
pos = [1, 2, 4]
amount = [10, 7, 7]
output = [3, 1, 0]
From stall 1 with 10: buy 2 + 3 + 5 = 10, so 3 items. From stall 2 with 7: 3 fits, 3 + 5 does not, so 1. From stall 4 with 7: the only item costs 8, so 0.
Example 2
prices = [1, 1, 1]
pos = [1, 3]
amount = [100, 0]
output = [3, 0]
A brute-force walk per query is O(n * q). Aim for better.
Approach
Key observation. Prices never go down along the row. From a starting stall, the cheapest item you can still buy is the one at the starting stall, the next cheapest is the one after it, and so on. So the best plan for any budget is always "buy at the start, then the next stall, then the next", stopping when the next price no longer fits. The answer to a query is the largest k such that the first k prices of the suffix fit in the budget.
Why greedy is safe: any set of k stalls from the suffix costs at least as much as the first k stalls of the suffix, because the i-th cheapest chosen price is at least the i-th price of the suffix. If any k items fit, the first k fit.
Brute force. For each query, walk from pos and add prices until the budget runs out. That is O(n) per query, O(n * q) total. With n and q both up to 10^5 this is 10^10 steps, too slow, but it is the natural first answer and a good test oracle.
Optimal. Build prefix sums once: pre[i] = prices[0] + ... + prices[i-1]. The cost of buying stalls s .. s+k-1 (0-indexed s = pos - 1) is pre[s + k] - pre[s]. That is non-decreasing in k because prices are non-negative, so binary search for the largest end index e with pre[e] <= pre[s] + amount. The answer is e - s. In Python that is one bisect_right call per query.
Invariants.
-
preis non-decreasing (prices are non-negative), sobisectis valid. - The search is restricted to
ein[s, n], so the answer is never negative and never runs past the end. - Python ints do not overflow; in Java or C++ use 64-bit sums (10^5 prices of 10^9 each exceed 32 bits).
Code
from bisect import bisect_right
from itertools import accumulate
def max_items(prices: list[int], pos: list[int], amount: list[int]) -> list[int]:
"""For each query j, the most items buyable from stalls pos[j]..n
(1-indexed, at most one per stall) with total cost <= amount[j].
prices must be non-decreasing and non-negative."""
n = len(prices)
pre = [0, *accumulate(prices)] # pre[i] = sum(prices[:i])
result: list[int] = []
for p, budget in zip(pos, amount):
s = p - 1 # 0-indexed start
if s < 0 or s >= n or budget < 0:
result.append(0)
continue
# Largest e in [s, n] with pre[e] - pre[s] <= budget.
e = bisect_right(pre, pre[s] + budget, lo=s, hi=n + 1) - 1
result.append(e - s)
return result
def max_items_brute(prices: list[int], pos: list[int], amount: list[int]) -> list[int]:
out = []
for p, budget in zip(pos, amount):
count, spent = 0, 0
for price in prices[p - 1:]:
if spent + price > budget:
break
spent += price
count += 1
out.append(count)
return out
if __name__ == "__main__":
import random
assert max_items([2, 3, 5, 8], [1, 2, 4], [10, 7, 7]) == [3, 1, 0]
assert max_items([1, 1, 1], [1, 3], [100, 0]) == [3, 0]
assert max_items([0, 0, 4], [1], [0]) == [2]
assert max_items([5], [1, 1], [4, 5]) == [0, 1]
for _ in range(500):
n = random.randint(1, 30)
prices = sorted(random.randint(0, 20) for _ in range(n))
q = random.randint(1, 10)
pos = [random.randint(1, n) for _ in range(q)]
amount = [random.randint(0, 200) for _ in range(q)]
assert max_items(prices, pos, amount) == max_items_brute(prices, pos, amount)
print("ok")
Complexity
- Preprocessing (prefix sums):
O(n)time,O(n)space. - Each query:
O(log n)time,O(1)extra space. - Total:
O(n + q log n)time,O(n + q)space including the output. - Brute force for comparison:
O(n * q)time,O(1)extra space.
Test cases
-
prices = [2, 3, 5, 8],pos = [1, 2, 4],amount = [10, 7, 7]->[3, 1, 0](2+3+5 = 10 fits exactly; 3 fits but 3+5 does not; 8 > 7). -
prices = [1, 1, 1],pos = [1, 3],amount = [100, 0]->[3, 0](budget larger than everything buys the whole suffix; zero budget buys nothing). -
prices = [0, 0, 4],pos = [1],amount = [0]->[2](free items are always taken). -
prices = [5],pos = [1, 1],amount = [4, 5]->[0, 1](single stall; exact budget counts). - Start at the last stall:
prices = [1, 2, 3],pos = [3],amount = [3]->[1]. - Large values:
n = 10^5, every price10^9,amount = 10^14-> whole suffix; check 64-bit sums in other languages. - Randomised check against the brute force on small sorted arrays.
Follow-ups
-
Prices are not sorted. Greedy still says "buy the
kcheapest items in the suffix", but they are no longer the firstk. Answer queries offline: sort queries byposdescending, insert prices into a Fenwick tree indexed by price rank (count and sum per rank) asposmoves left, then walk down the Fenwick tree (binary lifting) to find how many of the cheapest items fit. Handle a partial rank bucket with duplicate prices by takingremaining // priceof them.O((n + q) log n). Online, use a persistent segment tree over price ranks. -
Range queries
[l, r]instead of a suffix. With sorted prices this is the same binary search withhi = r + 1. With unsorted prices, use a persistent segment tree (versionrminus versionl - 1) and descend by sum,O(log n)per query. -
Price updates between queries (sorted order kept). Replace the prefix array with a Fenwick tree of prices. Find the answer with Fenwick binary lifting on target
prefix(s) + amount.O(log n)per update and query. -
Each stall has a stock count
c[i]. Prefix sums ofc[i] * prices[i]and ofc[i]; binary search the last stall you can fully buy out, then addremaining // prices[e]items from the next stall (capped by its stock). -
Maximise items, then minimise spend, or report which stalls. The greedy set is already the cheapest set of that size, so the spend is
pre[e] - pre[s]and the stalls ares .. e-1; return them alongside the count.
Interviewer expectations
Mid-level (L4)
- Spots that sorted prices make the greedy "take the next stall" correct and can say why.
- Writes the
O(n * q)loop, then moves to prefix sums with a binary search. - Gets 1-indexing and the "exact budget counts" boundary right; tests empty-result and full-suffix cases.
Senior (L5)
- Goes straight to
O(n + q log n)with a short exchange argument for the greedy. - Uses
bisect(or a hand-written search) with correct bounds so results never go negative or pastn; mentions 64-bit overflow in typed languages. - Handles follow-ups 3 and 4 (Fenwick for updates, stock counts) cleanly.
Staff (L6+)
- Solves the unsorted-prices version: offline Fenwick over price ranks with binary lifting, or a persistent segment tree for online and range queries.
- Discusses trade-offs between offline and online answering, memory of persistent structures, and duplicate prices in the descent.
- Validates with a brute-force oracle and randomised tests, and states the complexity of each variant precisely.