AlgoMaster Logo

Design a Food Rating System

mediumFrequency7 min readUpdated June 23, 2026

Understanding the Problem

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.

Key Constraints:

  • 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.
  • All food names are distinct -> Food names work as unique keys in hash maps.
  • food and cuisine arguments are always valid -> No need to handle missing keys.

Approach 1: Brute Force (Linear Scan)

Intuition

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.

Algorithm

  1. In the constructor, build three hash maps:
    • 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.
  2. For changeRating(food, newRating): update foodToRating[food] to newRating.
  3. For 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.

Example Walkthrough

1Initialize: build foodRating and foodCuisine maps from input arrays
kimchi
:
9
miso
:
12
sushi
:
8
moussaka
:
15
ramen
:
14
bulgogi
:
7
1/9

Code

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.

Approach 2: Sorted Set (Optimal)

Intuition

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.

Algorithm

  1. In the constructor:
    • Build a foodToRating map and a foodToCuisine map, same as before.
    • For each cuisine, create a 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).
  2. For changeRating(food, newRating):
    • Look up the food's cuisine and current rating.
    • Remove the entry (currentRating, food) from the cuisine's sorted set.
    • Update foodToRating[food] to newRating.
    • Insert the entry (newRating, food) into the cuisine's sorted set.
  3. For highestRated(cuisine):
    • Return the first element of the cuisine's sorted set. This is the food with the highest rating (and smallest name on ties).

Example Walkthrough

1Initialize: build TreeSet per cuisine, ordered by (-rating, name)
korean
:
[(kimchi,9), (bulgogi,7)]
japanese
:
[(ramen,14), (miso,12), (sushi,8)]
greek
:
[(moussaka,15)]
1/8

Code