+ most beautiful item for each query + — 9/12/24 +
+problem statement
+
+ Given an array items of \((price, beauty)\) tuples,
+ answer each integer query of \(queries\). The answer to some
+ query[i] is the maximum beauty of an item with
+ \(price\leq\)items[i][0].
+
understanding the problem
++ Focus on one aspect of the problem at a time. To answer a query, + we need to have considered: +
+-
+
- Items with a non-greater price +
- The beauty of all such items +
+ Given some query, how can we efficiently identify the
+ “last” item with an acceptable price? Leverage the
+ most common pre-processing algorithm: sorting. Subsequently, we
+ can binary search items (keyed by price, of course)
+ to identify all considerable items in \(O(lg(n))\).
+
+ Great. Now we need to find the item with the largest beauty.
+ Naïvely considering all the element is a
+ correct approach—but is it correct? Considering our
+ binary search \(O(lg(n))\) and beauty search \(O(n)\) across
+ \(\Theta(n)\) queries with
+ len(items)<=len(queries)\(\leq10^5\), an
+ \(O(n^2lg(n))\) approach is certainly unacceptable.
+
+ Consider alternative approaches to responding to our queries. It + is clear that answering them in-order yields no benefit (i.e. we + have to consider each item all over again, per query)—could + we answer them in another order to save computations? +
+
+ Visualizing our items from left-to-right, we's interested in
+ both increasing beauty and prices. If we can scan our items left
+ to right, we can certainly “accumulate” a running
+ maximal beauty. We can leverage sorting once again to answer our
+ queries left-to-right, then re-order them appropriately before
+ returning a final answer. Sorting both queries and
+ items with a linear scan will take \(O(nlg(n))\)
+ time, meeting the constraints.
+
fleshing out the approach
++ A few specifics need to be understood before coding up the approach: +
+-
+
-
+ Re-ordering the queries: couple
query[i]with +i, then sort. When responding to queries in sorted + order, we know where to place them in an output + container—indexi. +
+ -
+ The linear scan: accumulate a running maximal beauty, starting at
+ index
0. For some queryquery, we want + to consider all items with price less than or equal to +query. Therefore, loop until this condition is + violated— the previous index will represent the last + considered item. +
+ -
+ Edge cases: it's perfectly possible the last considered item
+ is invalid (consider a query cheaper than the cheapest item).
+ Return
0as specified by the problem constraints. +
+
+ carrying out the plan +
+vector maximumBeauty(vector>& items, vector& queries) {
+ std::sort(items.begin(), items.end());
+ std::vector<pair<int, int>> sorted_queries;
+ sorted_queries.reserve(queries.size());
+ // couple queries with their indices
+ for (size_t i = 0; i < queries.size(); ++i) {
+ sorted_queries.emplace_back(queries[i], i);
+ }
+ std::sort(sorted_queries.begin(), sorted_queries.end());
+
+ int beauty = items[0][1];
+ size_t i = 0;
+ std::vector ans(queries.size());
+
+ for (const auto [query, index] : sorted_queries) {
+ while (i < items.size() && items[i][0] <= query) {
+ beauty = std::max(beauty, items[i][1]);
+ ++i;
+ }
+ // invariant: items[i - 1] is the rightmost considerable item
+ ans[index] = i > 0 && items[i - 1][0] <= query ? beauty : 0;
+ }
+
+ return std::move(ans);
+