We need to build a data structure that manages food items, each belonging to a cuisine and having a rating. Two operations must be supported: updating a food's rating and querying the highest-rated food for a given cuisine (with lexicographic tiebreaking).
The challenge is the interplay between updates and queries. If we only needed queries, we could sort each cuisine's food list once and be done. Ratings change, so we need a structure that handles both efficiently. A linear scan of every food in a cuisine on each query works, but with up to 2 * 10^4 total calls it can be slow when a cuisine is large.
What we need is a per-cuisine ordered collection where we can find the maximum quickly and also update individual elements. A sorted set (TreeSet in Java, SortedSet in C#, std::set in C++, SortedList or BTreeSet elsewhere) gives this directly. In languages without a built-in ordered set, a heap with lazy deletion achieves the same time bounds.
n <= 2 * 10^4 and at most 2 * 10^4 calls -> An O(k) scan per query, where k is the cuisine size, reaches O(n) per query when one cuisine holds all foods. With many queries that approaches 4 * 10^8 operations, so an O(log n) per-operation structure is the safer target.ratings[i] <= 10^8 -> Ratings fit in a 32-bit signed int (max ~2.1 * 10^9), so no overflow concerns.food and cuisine arguments are always valid -> No need to handle missing keys.Store each food's information in hash maps, and whenever we need the highest-rated food for a cuisine, scan through all foods of that cuisine and pick the best one. There is no ordering to maintain.
changeRating updates the food's rating in a hash map, which is O(1). highestRated iterates through every food in the given cuisine, comparing ratings and breaking ties by name. This scan is O(k), where k is the number of foods in that cuisine.
foodToRating: maps each food name to its rating.foodToCuisine: maps each food name to its cuisine.cuisineToFoods: maps each cuisine to its list of food names.changeRating(food, newRating): update foodToRating[food] to newRating.highestRated(cuisine): iterate through all foods in cuisineToFoods[cuisine], track the one with the highest rating. If two foods have the same rating, pick the lexicographically smaller name.changeRating: O(1). Single hash map update.highestRated: O(k), where k is the number of foods in the given cuisine. In the worst case, all foods belong to one cuisine, so k = n.Every highestRated call rescans the entire cuisine, repeating work that the previous query already did. The next approach keeps each cuisine's foods in sorted order so the highest-rated one is always available without a scan.
Instead of scanning every food on each query, maintain a sorted set per cuisine. Order entries by rating descending, then by name ascending for tiebreaking. The highest-rated food is then always the first element, so a query is O(1): peek at the front.
The work moves to changeRating. The set is ordered by rating, so an in-place value update would leave the entry in the wrong position. We remove the old entry, update the rating, then insert the new entry. Removal and insertion are each O(log k) in a balanced-BST-backed set, where k is the number of foods in that cuisine.
The languages with a built-in ordered set use it directly: TreeSet in Java, SortedSet in C#, std::set in C++, SortedList (from sortedcontainers, available on LeetCode) in Python, and BTreeSet in Rust. Go, JavaScript, and TypeScript have no built-in ordered set, so they reach the same time bounds with a heap and lazy deletion, described after the sorted-set code.
foodToRating map and a foodToCuisine map, same as before.TreeSet (or equivalent sorted set) that orders entries by (-rating, name). This way the first element is always the highest-rated food (with lexicographic tiebreaking).changeRating(food, newRating):foodToRating[food] to newRating.highestRated(cuisine):The sorted set's comparison key encodes both query rules at once: rating descending, then name ascending. So the first element is, by construction, the highest-rated food with the smallest name on ties. There is nothing to compute at query time.
The ordering of an entry depends on its rating, so an entry must be removed using its old key before the rating changes. If we updated foodToRating first, the set could no longer locate the old entry to remove it, leaving a stale duplicate. The order is: remove old key, update rating, insert new key.
The heap variant (Go, JavaScript, TypeScript) cannot remove an arbitrary entry cheaply, so changeRating pushes a new entry and leaves the old one in place. A query pops entries off the top while their stored rating disagrees with the current rating in foodToRating, stopping at the first entry that matches. That first matching entry is the true maximum, because any entry above it had a higher stored rating that is now outdated and gets discarded. Each entry is pushed once and popped at most once, so the work is amortized O(log n) per operation.
changeRating: O(log n). One removal and one insertion in the sorted set, each O(log k) with k <= n. The heap variant is also O(log n) for the push.highestRated: O(1) for the sorted-set variant (read the first element). For the heap variant it is amortized O(log n), since each stale entry is popped at most once over the lifetime of the structure.changeRating calls, because stale entries stay in the heap until a query pops them.