Quick Overview

Maintain ratings for dishes that each belong to one cuisine, apply rating changes, and report the highest-rated dish in a cuisine, breaking ties by the alphabetically smallest name. It tests choosing data structures that keep per-cuisine maximum queries fast while ratings keep changing.

Track Dish Ratings and Report the Top-Rated Dish in Each Cuisine

Company: Apple

Role: Software Engineer

Category: Coding & Algorithms

Difficulty: medium

Interview Round: Technical Screen

A rating tracker keeps a rating for every dish on a menu, and each dish belongs to exactly one cuisine. Ratings change over time, and at any moment the tracker must report the highest-rated dish within a given cuisine. In the interview this was written as a class (a constructor, a method that changes a rating, and a method that reports the top dish of a cuisine) together with the candidate's own test cases; here the class is driven by a single function that replays a list of operations. ### Function Signature ```python def run_dish_ratings(foods: list[str], cuisines: list[str], ratings: list[int], operations: list[list[str]]) -> list[str]: ``` ### Rules - Dish `foods[i]` belongs to cuisine `cuisines[i]` and starts with rating `ratings[i]`. A dish's cuisine never changes. - The operations are applied in order. Each one is either: - `["change", food, new_rating]`: set the rating of dish `food` to the integer written in decimal in the string `new_rating`. Setting a dish to its current rating is allowed and changes nothing. - `["highest", cuisine]`: report the name of the dish in `cuisine` with the highest current rating. If several dishes share that rating, report the name that is smallest in lexicographic order: compare character by character, and a name that is a proper prefix of another comes first, so `"pie"` comes before `"pierogi"`. - Return the reports of all `"highest"` operations, in the order those operations appear. Return an empty list if there are none. ### Constraints - `1 <= len(foods) <= 2 * 10^4`, and `len(cuisines) == len(ratings) == len(foods)` - Every dish name and cuisine name has 1 to 10 lowercase English letters. Dish names are distinct; many dishes can share a cuisine. - `1 <= ratings[i] <= 10^9`. Every `new_rating` is the decimal string of an integer in the same range, with no sign and no leading zeros. - `1 <= len(operations) <= 2 * 10^4` - Every `"change"` names a dish in `foods`, and every `"highest"` names a cuisine that appears in `cuisines`. - All values fit in a 32-bit signed integer. ### Examples **Example 1** ```text Input: foods = ["tacos", "pho", "burrito", "ramen", "nachos"] cuisines = ["mexican", "vietnamese", "mexican", "japanese", "mexican"] ratings = [7, 9, 8, 6, 8] operations = [["highest", "mexican"], ["change", "tacos", "10"], ["highest", "mexican"], ["change", "tacos", "5"], ["highest", "mexican"], ["change", "burrito", "3"], ["highest", "mexican"], ["highest", "japanese"]] Output: ["burrito", "tacos", "burrito", "nachos", "ramen"] ``` The Mexican dishes start at tacos 7, burrito 8 and nachos 8, and the tie at 8 goes to `"burrito"`. Tacos then rises to 10 and leads. After it drops to 5, burrito and nachos tie at 8 again and `"burrito"` wins. After burrito drops to 3, nachos leads with 8. Ramen is the only Japanese dish. **Example 2** ```text Input: foods = ["pierogi", "pie"] cuisines = ["comfort", "comfort"] ratings = [5, 5] operations = [["highest", "comfort"], ["change", "pierogi", "6"], ["highest", "comfort"], ["change", "pie", "6"], ["highest", "comfort"]] Output: ["pie", "pierogi", "pie"] ``` `"pie"` is a proper prefix of `"pierogi"`, so it wins both ties; in between, pierogi leads alone with 6.

Overview: Maintain ratings for dishes that each belong to one cuisine, apply rating changes, and report the highest-rated dish in a cuisine, breaking ties by the alphabetically smallest name. It tests choosing data structures that keep per-cuisine maximum queries fast while ratings keep changing.

A rating tracker keeps a rating for every dish on a menu, and each dish belongs to exactly one cuisine. Ratings change over time, and at any moment the tracker must report the highest-rated dish within a given cuisine. Here the tracker is driven by a single function, `run_dish_ratings(foods, cuisines, ratings, operations)`, which sets up the starting menu and replays a list of operations in order. **Rules** - Dish `foods[i]` belongs to cuisine `cuisines[i]` and starts with rating `ratings[i]`. A dish's cuisine never changes. - The operations are applied in order. Each one is either: - `["change", food, new_rating]`: set the rating of dish `food` to the integer written in decimal in the string `new_rating`. Setting a dish to its current rating is allowed and changes nothing. - `["highest", cuisine]`: report the name of the dish in `cuisine` with the highest current rating. If several dishes share that rating, report the name that is smallest in lexicographic order: compare character by character, and a name that is a proper prefix of another comes first, so `"pie"` comes before `"pierogi"`. - Return the reports of all `"highest"` operations, in the order those operations appear. Return an empty list if there are none. **Constraints** - `1 <= len(foods) <= 2 * 10^4`, and `len(cuisines) == len(ratings) == len(foods)` - Every dish name and cuisine name has 1 to 10 lowercase English letters. Dish names are distinct; many dishes can share a cuisine. - `1 <= ratings[i] <= 10^9`. Every `new_rating` is the decimal string of an integer in the same range, with no sign and no leading zeros. - `1 <= len(operations) <= 2 * 10^4` - Every `"change"` names a dish in `foods`, and every `"highest"` names a cuisine that appears in `cuisines`. - All values fit in a 32-bit signed integer: no rating exceeds 2^31 - 1, so `int` is enough in Java and C++. **Example 1** ```text Input: foods = ["tacos", "pho", "burrito", "ramen", "nachos"] cuisines = ["mexican", "vietnamese", "mexican", "japanese", "mexican"] ratings = [7, 9, 8, 6, 8] operations = [["highest", "mexican"], ["change", "tacos", "10"], ["highest", "mexican"], ["change", "tacos", "5"], ["highest", "mexican"], ["change", "burrito", "3"], ["highest", "mexican"], ["highest", "japanese"]] Output: ["burrito", "tacos", "burrito", "nachos", "ramen"] ``` The Mexican dishes start at tacos 7, burrito 8 and nachos 8, and the tie at 8 goes to `"burrito"`. Tacos then rises to 10 and leads. After it drops to 5, burrito and nachos tie at 8 again and `"burrito"` wins. After burrito drops to 3, nachos leads with 8. Ramen is the only Japanese dish. **Example 2** ```text Input: foods = ["pierogi", "pie"] cuisines = ["comfort", "comfort"] ratings = [5, 5] operations = [["highest", "comfort"], ["change", "pierogi", "6"], ["highest", "comfort"], ["change", "pie", "6"], ["highest", "comfort"]] Output: ["pie", "pierogi", "pie"] ``` `"pie"` is a proper prefix of `"pierogi"`, so it wins both ties; in between, pierogi leads alone with 6.

Constraints

  • 1 <= len(foods) <= 2 * 10^4, and len(cuisines) == len(ratings) == len(foods)
  • Every dish name and cuisine name has 1 to 10 lowercase English letters. Dish names are distinct; many dishes can share a cuisine.
  • 1 <= ratings[i] <= 10^9. Every new_rating is the decimal string of an integer in the same range, with no sign and no leading zeros.
  • 1 <= len(operations) <= 2 * 10^4
  • Each operation is either a 3-element ["change", food, new_rating] or a 2-element ["highest", cuisine].
  • Every "change" names a dish in foods, and every "highest" names a cuisine that appears in cuisines.
  • All values fit in a 32-bit signed integer (no rating exceeds 2^31 - 1).

Examples

Input: (['tacos', 'pho', 'burrito', 'ramen', 'nachos'], ['mexican', 'vietnamese', 'mexican', 'japanese', 'mexican'], [7, 9, 8, 6, 8], [['highest', 'mexican'], ['change', 'tacos', '10'], ['highest', 'mexican'], ['change', 'tacos', '5'], ['highest', 'mexican'], ['change', 'burrito', '3'], ['highest', 'mexican'], ['highest', 'japanese']])

Expected Output: ['burrito', 'tacos', 'burrito', 'nachos', 'ramen']

Explanation: Source Example 1: the tie at 8 goes to burrito, tacos rises to 10 (an integer, so it beats 8) and leads, the tie returns after tacos drops to 5, nachos leads after burrito drops to 3, and ramen is the only Japanese dish.

Input: (['pierogi', 'pie'], ['comfort', 'comfort'], [5, 5], [['highest', 'comfort'], ['change', 'pierogi', '6'], ['highest', 'comfort'], ['change', 'pie', '6'], ['highest', 'comfort']])

Expected Output: ['pie', 'pierogi', 'pie']

Explanation: Source Example 2: 'pie' is a proper prefix of 'pierogi' and wins both ties; pierogi leads alone at 6 in between.

Hints

  1. A dish's cuisine never changes, so a "highest" query only ever concerns the dishes of one cuisine; you can group dishes by cuisine once, up front.
  2. There can be 2 * 10^4 dishes and 2 * 10^4 operations, so rescanning a whole cuisine on every "highest" may be too slow; think about how to keep each cuisine's dishes ordered by rating and name as ratings change.
  3. new_rating arrives as text: convert it to an integer before comparing, because as text "9" sorts after "10".

Loading coding console...

Show the approach

Approach

Algorithm: a dish's cuisine never changes, so group the dishes by cuisine once. Within each cuisine, order dishes by the key (rating descending, name ascending); the answer to a "highest" query is the first dish in that order. Plain string comparison already puts a proper prefix first ("pie" < "pierogi").

Java and C++ keep an ordered set per cuisine. A change removes the dish under its old key, updates the rating and reinserts it, so each set always holds exactly one entry per dish with its current rating and its first element is the answer.

Python and JavaScript keep a priority queue per cuisine with lazy deletion. A change records the dish's current rating and pushes a new (rating, name) entry; an entry is stale when its rating differs from the dish's current rating. Invariant: every dish always has at least one entry that matches its current rating (the one pushed when that rating was set, or its starting entry), because entries are only discarded while they are stale. A "highest" query discards stale entries from the top; the first valid top is at least as good as every valid entry, and the valid entries are exactly the current (rating, name) states of the cuisine's dishes, so it is the correct answer.

Edge cases: new_rating is parsed as an integer (as text, "9" would beat "10"); a change to the current rating changes nothing; a dish raised and then lowered back leaves only a stale higher entry; operations with no "highest" return an empty list; dish names and cuisine names are looked up in separate maps, so a dish may share its name with a cuisine.

Time complexity:
O((n + q) log(n + q)), where n = len(foods) and q = len(operations)
Space complexity:
O(n + q)