Design a Food Rating System

MediumDesign · Hash Map + Ordered Set🧩 Data Structure DesignLeetCode #2353 ↗Updated

Problem

Build FoodRatings(foods, cuisines, ratings), where food i has a unique name, a cuisine and a rating. Support changeRating(food, newRating) and highestRated(cuisine), which returns the name of the best-rated food of that cuisine; ties go to the lexicographically smallest name.
Inputfoods=[kimchi,miso,sushi,moussaka,ramen,bulgogi] cuisines=[korean,japanese,japanese,greek,japanese,korean] ratings=[9,12,8,15,14,7]; highestRated(korean)Outputkimchi
InputhighestRated(japanese); changeRating(sushi,16); highestRated(japanese)Outputramen, sushi
InputchangeRating(ramen,16); highestRated(japanese)Outputramen (tie, smaller name)

1 ≤ n ≤ 2·104  ·  1 ≤ ratings[i] ≤ 108  ·  names ≤ 10 lowercase letters  ·  ≤ 2·104 calls  ·  queried food/cuisine always exists

Try it yourself

Java
class FoodRatings {
    public FoodRatings(String[] foods, String[] cuisines, int[] ratings) {
        
    }

    public void changeRating(String food, int newRating) {
        
    }

    public String highestRated(String cuisine) {
        
    }
}
Your code is saved in this browser.Submit on LeetCode ↗
Key insight
Group foods by cuisine and keep each group sorted by (rating desc, name asc). A rating change is just remove → update → re-insert in that one group, and the answer is the group's first element.
PatternData Structure Design

Instinct Implement a class whose methods must all hit a time bound (often O(1)) - write each method's target cost, then glue a hash map to a structure that keeps order.

Memory hack Finder plus keeper - a hash map finds it fast, a list/heap/array keeps the order, and every method updates both.

Open the Data Structure Design guide →

Approaches

Brute ForceScan Every Food on Each Query

Time change O(1), query O(n)Space O(n)

Keep two maps: food → rating and food → cuisine. A rating change is a single map write. highestRated walks all foods, filters by cuisine, and keeps the best one using the rating-then-name rule.

Java
class FoodRatings {
    private final Map<String, Integer> rating = new HashMap<>();
    private final Map<String, String> cuisineOf = new HashMap<>();
    public FoodRatings(String[] foods, String[] cuisines, int[] ratings) {
        for (int i = 0; i < foods.length; i++) {
            rating.put(foods[i], ratings[i]);
            cuisineOf.put(foods[i], cuisines[i]);
        }
    }
    public void changeRating(String food, int newRating) {
        rating.put(food, newRating);
    }
    public String highestRated(String cuisine) {
        String best = null;
        for (Map.Entry<String, Integer> e : rating.entrySet()) {
            String food = e.getKey();
            if (!cuisineOf.get(food).equals(cuisine)) continue;
            int r = e.getValue();
            if (best == null || r > rating.get(best)
                    || (r == rating.get(best) && food.compareTo(best) < 0)) {
                best = food;
            }
        }
        return best;
    }
}

Simple and cheap to update, but every query touches all n foods.

At a glance

LevelApproachTimeSpace
Brute ForceScan Every Food on Each Querychange O(1), query O(n)O(n)
BetterPer-Cuisine Max-Heap with Lazy DeletionO(log m) amortizedO(n + updates)
OptimalPer-Cuisine TreeSet (Remove & Re-insert)O(log n) per opO(n)
Common pitfalls
  • Updating the rating before removing from the TreeSet — the removal silently fails.
  • Comparing boxed Integer ratings with == (breaks above 127).
  • Forgetting the name tie-break, so equal ratings return an arbitrary food.
Interview tips
  • Clarify that names are unique and queried cuisines always exist.
  • Present the lazy heap first, then explain why an ordered set avoids stale entries.
  • Walk through the tie example to show the comparator's second key.
Pattern
Hash map + ordered set per group → “best item in a category” with frequent score updates.
My notes saved in this browser