AlgoMaster Logo

Design a Food Rating System

Last Updated: April 4, 2026

medium
4 min read

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 interesting challenge here is the interplay between updates and queries. If we only needed queries, we could sort each cuisine's food list once and be done. But ratings change, so we need a structure that handles both efficiently. The naive approach would scan all foods in a cuisine on every query, but with up to 2 * 10^4 total calls, we want something faster.

The key observation is that we need a per-cuisine ordered collection where we can quickly find the maximum and also update individual elements. This is a classic use case for sorted sets (like TreeSet in Java or SortedList in Python) or heaps with lazy deletion.

Key Constraints:

  • n <= 2 * 10^4 and at most 2 * 10^4 calls -> Total operations are at most ~4 10^4. Even O(n) per operation would give ~4 10^8, which is borderline. O(n log n) per operation is safe, but O(n^2) total is too slow.
  • All food names are distinct -> We can use food names as unique keys in hash maps.
  • food will always be valid -> No need to handle missing food names in changeRating.
  • cuisine will always be valid -> No need to handle empty cuisines in highestRated.

Approach 1: Brute Force (Linear Scan)

Intuition

The simplest design: store each food's information in hash maps and, whenever we need the highest-rated food for a cuisine, just scan through all foods of that cuisine and pick the best one. No fancy data structures, no ordering to maintain. It's straightforward and correct.

For changeRating, we just update the food's rating in a hash map. That's O(1). For highestRated, we iterate through all foods belonging to the given cuisine, compare ratings, and handle tiebreaking 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 scans through every food in the requested cuisine. If one cuisine has thousands of foods and we make thousands of queries, we're doing redundant work. What if we could maintain the foods in sorted order so that the highest-rated one is always immediately accessible?

Approach 2: Sorted Set (Optimal)

Intuition

Instead of scanning all foods on every query, we can maintain a sorted set for each cuisine. The sorted set keeps food items ordered by rating (descending) and then by name (ascending for tiebreaking). This way, the highest-rated food is always at the front of the set, giving us O(1) query time (just peek at the first element).

The trick is handling changeRating. When a food's rating changes, we can't just update the value in place because sorted sets are ordered by the value. We need to remove the old entry (old rating, food name), update the rating, then insert the new entry (new rating, food name). Both removal and insertion are O(log k) in a balanced BST-backed sorted set, where k is the number of foods in that cuisine.

In Java, TreeSet is perfect for this. We define a custom comparator that sorts by rating descending, then by name ascending. In Python, we use SortedList from the sortedcontainers library (which LeetCode supports). In C++, we use set with a custom comparator or store pairs sorted appropriately.

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