From bcf6732327e4d12a871a972b7cfb2ca565e3f3a3 Mon Sep 17 00:00:00 2001 From: yanglbme Date: Thu, 27 Feb 2025 11:14:51 +0800 Subject: [PATCH] feat: add solutions to lc problem: No.2353 No.2353.Design a Food Rating System --- .../README.md | 169 +++++++++++++++--- .../README_EN.md | 169 +++++++++++++++--- .../Solution.cpp | 32 ++-- .../Solution.go | 54 ++++++ .../Solution.java | 39 ++++ .../Solution.py | 21 ++- 6 files changed, 400 insertions(+), 84 deletions(-) create mode 100644 solution/2300-2399/2353.Design a Food Rating System/Solution.go create mode 100644 solution/2300-2399/2353.Design a Food Rating System/Solution.java diff --git a/solution/2300-2399/2353.Design a Food Rating System/README.md b/solution/2300-2399/2353.Design a Food Rating System/README.md index 2ed81fb0352af..f8ef26e6dae2a 100644 --- a/solution/2300-2399/2353.Design a Food Rating System/README.md +++ b/solution/2300-2399/2353.Design a Food Rating System/README.md @@ -95,7 +95,19 @@ foodRatings.highestRated("japanese"); // 返回 "ramen" -### 方法一 +### 方法一:哈希表 + 有序集合 + +我们可以使用哈希表 $\textit{d}$ 来存储每种烹饪方式下的食物,其中键是烹饪方式,值是一个有序集合,有序集合的每个元素是一个二元组 $(\textit{rating}, \textit{food})$,按照评分从高到低排序,如果评分相同,则按照食物名字的字典序从小到大排序。 + +我们还可以使用哈希表 $\textit{g}$ 来存储每种食物的评分和烹饪方式。即 $\textit{g}[\textit{food}] = (\textit{rating}, \textit{cuisine})$。 + +在构造函数中,我们遍历 $\textit{foods}$、$\textit{cuisines}$ 和 $\textit{ratings}$,将每种食物的评分和烹饪方式存储到 $\textit{d}$ 和 $\textit{g}$ 中。 + +在 $\textit{changeRating}$ 函数中,我们首先获取食物 $\textit{food}$ 的原评分 $\textit{oldRating}$ 和烹饪方式 $\textit{cuisine}$,然后更新 $\textit{g}[\textit{food}]$ 的评分为 $\textit{newRating}$,并从 $\textit{d}[\textit{cuisine}]$ 中删除 $(\textit{oldRating}, \textit{food})$,并将 $(\textit{newRating}, \textit{food})$ 添加到 $\textit{d}[\textit{cuisine}]$ 中。 + +在 $\textit{highestRated}$ 函数中,我们直接返回 $\textit{d}[\textit{cuisine}]$ 的第一个元素的食物名字即可。 + +时间复杂度方面,构造函数的时间复杂度为 $O(n \log n)$,其中 $n$ 是食物的数量。其余操作的时间复杂度为 $O(\log n)$。空间复杂度为 $O(n)$。 @@ -103,22 +115,22 @@ foodRatings.highestRated("japanese"); // 返回 "ramen" ```python class FoodRatings: - def __init__(self, foods: List[str], cuisines: List[str], ratings: List[int]): - self.mp = {} - self.t = defaultdict(lambda: SortedSet(key=lambda x: (-x[0], x[1]))) - for a, b, c in zip(foods, cuisines, ratings): - self.mp[a] = (b, c) - self.t[b].add((c, a)) + def __init__(self, foods: List[str], cuisines: List[str], ratings: List[int]): + self.d = defaultdict(SortedList) + self.g = {} + for food, cuisine, rating in zip(foods, cuisines, ratings): + self.d[cuisine].add((-rating, food)) + self.g[food] = (rating, cuisine) def changeRating(self, food: str, newRating: int) -> None: - b, c = self.mp[food] - self.mp[food] = (b, newRating) - self.t[b].remove((c, food)) - self.t[b].add((newRating, food)) + oldRating, cuisine = self.g[food] + self.g[food] = (newRating, cuisine) + self.d[cuisine].remove((-oldRating, food)) + self.d[cuisine].add((-newRating, food)) def highestRated(self, cuisine: str) -> str: - return self.t[cuisine][0][1] + return self.d[cuisine][0][1] # Your FoodRatings object will be instantiated and called as such: @@ -127,36 +139,78 @@ class FoodRatings: # param_2 = obj.highestRated(cuisine) ``` +#### Java + +```java +class FoodRatings { + private Map>> d = new HashMap<>(); + private Map> g = new HashMap<>(); + private final Comparator> cmp = (a, b) -> { + if (!a.getKey().equals(b.getKey())) { + return b.getKey().compareTo(a.getKey()); + } + return a.getValue().compareTo(b.getValue()); + }; + + public FoodRatings(String[] foods, String[] cuisines, int[] ratings) { + for (int i = 0; i < foods.length; ++i) { + String food = foods[i], cuisine = cuisines[i]; + int rating = ratings[i]; + d.computeIfAbsent(cuisine, k -> new TreeSet<>(cmp)).add(new Pair<>(rating, food)); + g.put(food, new Pair<>(rating, cuisine)); + } + } + + public void changeRating(String food, int newRating) { + Pair old = g.get(food); + int oldRating = old.getKey(); + String cuisine = old.getValue(); + g.put(food, new Pair<>(newRating, cuisine)); + d.get(cuisine).remove(new Pair<>(oldRating, food)); + d.get(cuisine).add(new Pair<>(newRating, food)); + } + + public String highestRated(String cuisine) { + return d.get(cuisine).first().getValue(); + } +} + +/** + * Your FoodRatings object will be instantiated and called as such: + * FoodRatings obj = new FoodRatings(foods, cuisines, ratings); + * obj.changeRating(food,newRating); + * String param_2 = obj.highestRated(cuisine); + */ +``` + #### C++ ```cpp -using pis = pair; - class FoodRatings { - map mp; - map> t; - public: FoodRatings(vector& foods, vector& cuisines, vector& ratings) { - int n = foods.size(); - for (int i = 0; i < n; ++i) { - string a = foods[i], b = cuisines[i]; - int c = ratings[i]; - mp[a] = pis(c, b); - t[b].insert(pis(-c, a)); + for (int i = 0; i < foods.size(); ++i) { + string food = foods[i], cuisine = cuisines[i]; + int rating = ratings[i]; + d[cuisine].insert({-rating, food}); + g[food] = {rating, cuisine}; } } void changeRating(string food, int newRating) { - pis& p = mp[food]; - t[p.second].erase(pis(-p.first, food)); - p.first = newRating; - t[p.second].insert(pis(-p.first, food)); + auto [oldRating, cuisine] = g[food]; + g[food] = {newRating, cuisine}; + d[cuisine].erase({-oldRating, food}); + d[cuisine].insert({-newRating, food}); } string highestRated(string cuisine) { - return t[cuisine].begin()->second; + return d[cuisine].begin()->second; } + +private: + unordered_map>> d; + unordered_map> g; }; /** @@ -167,6 +221,65 @@ public: */ ``` +#### Go + +```go +import ( + "github.com/emirpasic/gods/v2/trees/redblacktree" +) + +type pair struct { + rating int + food string +} + +type FoodRatings struct { + d map[string]*redblacktree.Tree[pair, struct{}] + g map[string]pair +} + +func Constructor(foods []string, cuisines []string, ratings []int) FoodRatings { + d := make(map[string]*redblacktree.Tree[pair, struct{}]) + g := make(map[string]pair) + + for i, food := range foods { + rating, cuisine := ratings[i], cuisines[i] + g[food] = pair{rating, cuisine} + + if d[cuisine] == nil { + d[cuisine] = redblacktree.NewWith[pair, struct{}](func(a, b pair) int { + return cmp.Or(b.rating-a.rating, strings.Compare(a.food, b.food)) + }) + } + d[cuisine].Put(pair{rating, food}, struct{}{}) + } + + return FoodRatings{d, g} +} + +func (this *FoodRatings) ChangeRating(food string, newRating int) { + p := this.g[food] + t := this.d[p.food] + + t.Remove(pair{p.rating, food}) + t.Put(pair{newRating, food}, struct{}{}) + + p.rating = newRating + this.g[food] = p +} + +func (this *FoodRatings) HighestRated(cuisine string) string { + return this.d[cuisine].Left().Key.food +} + +/** + * Your FoodRatings object will be instantiated and called as such: + * obj := Constructor(foods, cuisines, ratings); + * obj.ChangeRating(food,newRating); + * param_2 := obj.HighestRated(cuisine); + */ +``` + diff --git a/solution/2300-2399/2353.Design a Food Rating System/README_EN.md b/solution/2300-2399/2353.Design a Food Rating System/README_EN.md index b0dacdada3da3..974e88ba74b87 100644 --- a/solution/2300-2399/2353.Design a Food Rating System/README_EN.md +++ b/solution/2300-2399/2353.Design a Food Rating System/README_EN.md @@ -94,7 +94,19 @@ foodRatings.highestRated("japanese"); // return "ramen" -### Solution 1 +### Solution 1: Hash Table + Ordered Set + +We can use a hash table $\textit{d}$ to store the foods for each cuisine, where the key is the cuisine and the value is an ordered set. Each element in the ordered set is a tuple $(\textit{rating}, \textit{food})$, sorted by rating in descending order, and if the ratings are the same, sorted by food name in lexicographical order. + +We can also use a hash table $\textit{g}$ to store the rating and cuisine for each food. That is, $\textit{g}[\textit{food}] = (\textit{rating}, \textit{cuisine})$. + +In the constructor, we iterate through $\textit{foods}$, $\textit{cuisines}$, and $\textit{ratings}$, storing the rating and cuisine for each food in $\textit{d}$ and $\textit{g}$. + +In the $\textit{changeRating}$ function, we first get the original rating $\textit{oldRating}$ and cuisine $\textit{cuisine}$ of the food $\textit{food}$, then update the rating of $\textit{g}[\textit{food}]$ to $\textit{newRating}$, remove $(\textit{oldRating}, \textit{food})$ from $\textit{d}[\textit{cuisine}]$, and add $(\textit{newRating}, \textit{food})$ to $\textit{d}[\textit{cuisine}]$. + +In the $\textit{highestRated}$ function, we directly return the food name of the first element in $\textit{d}[\textit{cuisine}]$. + +In terms of time complexity, the constructor has a time complexity of $O(n \log n)$, where $n$ is the number of foods. The other operations have a time complexity of $O(\log n)$. The space complexity is $O(n)$. @@ -102,22 +114,22 @@ foodRatings.highestRated("japanese"); // return "ramen" ```python class FoodRatings: - def __init__(self, foods: List[str], cuisines: List[str], ratings: List[int]): - self.mp = {} - self.t = defaultdict(lambda: SortedSet(key=lambda x: (-x[0], x[1]))) - for a, b, c in zip(foods, cuisines, ratings): - self.mp[a] = (b, c) - self.t[b].add((c, a)) + def __init__(self, foods: List[str], cuisines: List[str], ratings: List[int]): + self.d = defaultdict(SortedList) + self.g = {} + for food, cuisine, rating in zip(foods, cuisines, ratings): + self.d[cuisine].add((-rating, food)) + self.g[food] = (rating, cuisine) def changeRating(self, food: str, newRating: int) -> None: - b, c = self.mp[food] - self.mp[food] = (b, newRating) - self.t[b].remove((c, food)) - self.t[b].add((newRating, food)) + oldRating, cuisine = self.g[food] + self.g[food] = (newRating, cuisine) + self.d[cuisine].remove((-oldRating, food)) + self.d[cuisine].add((-newRating, food)) def highestRated(self, cuisine: str) -> str: - return self.t[cuisine][0][1] + return self.d[cuisine][0][1] # Your FoodRatings object will be instantiated and called as such: @@ -126,36 +138,78 @@ class FoodRatings: # param_2 = obj.highestRated(cuisine) ``` +#### Java + +```java +class FoodRatings { + private Map>> d = new HashMap<>(); + private Map> g = new HashMap<>(); + private final Comparator> cmp = (a, b) -> { + if (!a.getKey().equals(b.getKey())) { + return b.getKey().compareTo(a.getKey()); + } + return a.getValue().compareTo(b.getValue()); + }; + + public FoodRatings(String[] foods, String[] cuisines, int[] ratings) { + for (int i = 0; i < foods.length; ++i) { + String food = foods[i], cuisine = cuisines[i]; + int rating = ratings[i]; + d.computeIfAbsent(cuisine, k -> new TreeSet<>(cmp)).add(new Pair<>(rating, food)); + g.put(food, new Pair<>(rating, cuisine)); + } + } + + public void changeRating(String food, int newRating) { + Pair old = g.get(food); + int oldRating = old.getKey(); + String cuisine = old.getValue(); + g.put(food, new Pair<>(newRating, cuisine)); + d.get(cuisine).remove(new Pair<>(oldRating, food)); + d.get(cuisine).add(new Pair<>(newRating, food)); + } + + public String highestRated(String cuisine) { + return d.get(cuisine).first().getValue(); + } +} + +/** + * Your FoodRatings object will be instantiated and called as such: + * FoodRatings obj = new FoodRatings(foods, cuisines, ratings); + * obj.changeRating(food,newRating); + * String param_2 = obj.highestRated(cuisine); + */ +``` + #### C++ ```cpp -using pis = pair; - class FoodRatings { - map mp; - map> t; - public: FoodRatings(vector& foods, vector& cuisines, vector& ratings) { - int n = foods.size(); - for (int i = 0; i < n; ++i) { - string a = foods[i], b = cuisines[i]; - int c = ratings[i]; - mp[a] = pis(c, b); - t[b].insert(pis(-c, a)); + for (int i = 0; i < foods.size(); ++i) { + string food = foods[i], cuisine = cuisines[i]; + int rating = ratings[i]; + d[cuisine].insert({-rating, food}); + g[food] = {rating, cuisine}; } } void changeRating(string food, int newRating) { - pis& p = mp[food]; - t[p.second].erase(pis(-p.first, food)); - p.first = newRating; - t[p.second].insert(pis(-p.first, food)); + auto [oldRating, cuisine] = g[food]; + g[food] = {newRating, cuisine}; + d[cuisine].erase({-oldRating, food}); + d[cuisine].insert({-newRating, food}); } string highestRated(string cuisine) { - return t[cuisine].begin()->second; + return d[cuisine].begin()->second; } + +private: + unordered_map>> d; + unordered_map> g; }; /** @@ -166,6 +220,65 @@ public: */ ``` +#### Go + +```go +import ( + "github.com/emirpasic/gods/v2/trees/redblacktree" +) + +type pair struct { + rating int + food string +} + +type FoodRatings struct { + d map[string]*redblacktree.Tree[pair, struct{}] + g map[string]pair +} + +func Constructor(foods []string, cuisines []string, ratings []int) FoodRatings { + d := make(map[string]*redblacktree.Tree[pair, struct{}]) + g := make(map[string]pair) + + for i, food := range foods { + rating, cuisine := ratings[i], cuisines[i] + g[food] = pair{rating, cuisine} + + if d[cuisine] == nil { + d[cuisine] = redblacktree.NewWith[pair, struct{}](func(a, b pair) int { + return cmp.Or(b.rating-a.rating, strings.Compare(a.food, b.food)) + }) + } + d[cuisine].Put(pair{rating, food}, struct{}{}) + } + + return FoodRatings{d, g} +} + +func (this *FoodRatings) ChangeRating(food string, newRating int) { + p := this.g[food] + t := this.d[p.food] + + t.Remove(pair{p.rating, food}) + t.Put(pair{newRating, food}, struct{}{}) + + p.rating = newRating + this.g[food] = p +} + +func (this *FoodRatings) HighestRated(cuisine string) string { + return this.d[cuisine].Left().Key.food +} + +/** + * Your FoodRatings object will be instantiated and called as such: + * obj := Constructor(foods, cuisines, ratings); + * obj.ChangeRating(food,newRating); + * param_2 := obj.HighestRated(cuisine); + */ +``` + diff --git a/solution/2300-2399/2353.Design a Food Rating System/Solution.cpp b/solution/2300-2399/2353.Design a Food Rating System/Solution.cpp index 44ed3a67f1240..593d1329e2e5c 100644 --- a/solution/2300-2399/2353.Design a Food Rating System/Solution.cpp +++ b/solution/2300-2399/2353.Design a Food Rating System/Solution.cpp @@ -1,30 +1,28 @@ -using pis = pair; - class FoodRatings { - map mp; - map> t; - public: FoodRatings(vector& foods, vector& cuisines, vector& ratings) { - int n = foods.size(); - for (int i = 0; i < n; ++i) { - string a = foods[i], b = cuisines[i]; - int c = ratings[i]; - mp[a] = pis(c, b); - t[b].insert(pis(-c, a)); + for (int i = 0; i < foods.size(); ++i) { + string food = foods[i], cuisine = cuisines[i]; + int rating = ratings[i]; + d[cuisine].insert({-rating, food}); + g[food] = {rating, cuisine}; } } void changeRating(string food, int newRating) { - pis& p = mp[food]; - t[p.second].erase(pis(-p.first, food)); - p.first = newRating; - t[p.second].insert(pis(-p.first, food)); + auto [oldRating, cuisine] = g[food]; + g[food] = {newRating, cuisine}; + d[cuisine].erase({-oldRating, food}); + d[cuisine].insert({-newRating, food}); } string highestRated(string cuisine) { - return t[cuisine].begin()->second; + return d[cuisine].begin()->second; } + +private: + unordered_map>> d; + unordered_map> g; }; /** @@ -32,4 +30,4 @@ class FoodRatings { * FoodRatings* obj = new FoodRatings(foods, cuisines, ratings); * obj->changeRating(food,newRating); * string param_2 = obj->highestRated(cuisine); - */ \ No newline at end of file + */ diff --git a/solution/2300-2399/2353.Design a Food Rating System/Solution.go b/solution/2300-2399/2353.Design a Food Rating System/Solution.go new file mode 100644 index 0000000000000..49140ce1d89c2 --- /dev/null +++ b/solution/2300-2399/2353.Design a Food Rating System/Solution.go @@ -0,0 +1,54 @@ +import ( + "github.com/emirpasic/gods/v2/trees/redblacktree" +) + +type pair struct { + rating int + food string +} + +type FoodRatings struct { + d map[string]*redblacktree.Tree[pair, struct{}] + g map[string]pair +} + +func Constructor(foods []string, cuisines []string, ratings []int) FoodRatings { + d := make(map[string]*redblacktree.Tree[pair, struct{}]) + g := make(map[string]pair) + + for i, food := range foods { + rating, cuisine := ratings[i], cuisines[i] + g[food] = pair{rating, cuisine} + + if d[cuisine] == nil { + d[cuisine] = redblacktree.NewWith[pair, struct{}](func(a, b pair) int { + return cmp.Or(b.rating-a.rating, strings.Compare(a.food, b.food)) + }) + } + d[cuisine].Put(pair{rating, food}, struct{}{}) + } + + return FoodRatings{d, g} +} + +func (this *FoodRatings) ChangeRating(food string, newRating int) { + p := this.g[food] + t := this.d[p.food] + + t.Remove(pair{p.rating, food}) + t.Put(pair{newRating, food}, struct{}{}) + + p.rating = newRating + this.g[food] = p +} + +func (this *FoodRatings) HighestRated(cuisine string) string { + return this.d[cuisine].Left().Key.food +} + +/** + * Your FoodRatings object will be instantiated and called as such: + * obj := Constructor(foods, cuisines, ratings); + * obj.ChangeRating(food,newRating); + * param_2 := obj.HighestRated(cuisine); + */ diff --git a/solution/2300-2399/2353.Design a Food Rating System/Solution.java b/solution/2300-2399/2353.Design a Food Rating System/Solution.java new file mode 100644 index 0000000000000..9a195e4cdf3b9 --- /dev/null +++ b/solution/2300-2399/2353.Design a Food Rating System/Solution.java @@ -0,0 +1,39 @@ +class FoodRatings { + private Map>> d = new HashMap<>(); + private Map> g = new HashMap<>(); + private final Comparator> cmp = (a, b) -> { + if (!a.getKey().equals(b.getKey())) { + return b.getKey().compareTo(a.getKey()); + } + return a.getValue().compareTo(b.getValue()); + }; + + public FoodRatings(String[] foods, String[] cuisines, int[] ratings) { + for (int i = 0; i < foods.length; ++i) { + String food = foods[i], cuisine = cuisines[i]; + int rating = ratings[i]; + d.computeIfAbsent(cuisine, k -> new TreeSet<>(cmp)).add(new Pair<>(rating, food)); + g.put(food, new Pair<>(rating, cuisine)); + } + } + + public void changeRating(String food, int newRating) { + Pair old = g.get(food); + int oldRating = old.getKey(); + String cuisine = old.getValue(); + g.put(food, new Pair<>(newRating, cuisine)); + d.get(cuisine).remove(new Pair<>(oldRating, food)); + d.get(cuisine).add(new Pair<>(newRating, food)); + } + + public String highestRated(String cuisine) { + return d.get(cuisine).first().getValue(); + } +} + +/** + * Your FoodRatings object will be instantiated and called as such: + * FoodRatings obj = new FoodRatings(foods, cuisines, ratings); + * obj.changeRating(food,newRating); + * String param_2 = obj.highestRated(cuisine); + */ diff --git a/solution/2300-2399/2353.Design a Food Rating System/Solution.py b/solution/2300-2399/2353.Design a Food Rating System/Solution.py index eea8494e76570..8da305a3aa4c1 100644 --- a/solution/2300-2399/2353.Design a Food Rating System/Solution.py +++ b/solution/2300-2399/2353.Design a Food Rating System/Solution.py @@ -1,20 +1,19 @@ class FoodRatings: def __init__(self, foods: List[str], cuisines: List[str], ratings: List[int]): - self.mp = {} - self.t = defaultdict(lambda: SortedSet(key=lambda x: (-x[0], x[1]))) - - for a, b, c in zip(foods, cuisines, ratings): - self.mp[a] = (b, c) - self.t[b].add((c, a)) + self.d = defaultdict(SortedList) + self.g = {} + for food, cuisine, rating in zip(foods, cuisines, ratings): + self.d[cuisine].add((-rating, food)) + self.g[food] = (rating, cuisine) def changeRating(self, food: str, newRating: int) -> None: - b, c = self.mp[food] - self.mp[food] = (b, newRating) - self.t[b].remove((c, food)) - self.t[b].add((newRating, food)) + oldRating, cuisine = self.g[food] + self.g[food] = (newRating, cuisine) + self.d[cuisine].remove((-oldRating, food)) + self.d[cuisine].add((-newRating, food)) def highestRated(self, cuisine: str) -> str: - return self.t[cuisine][0][1] + return self.d[cuisine][0][1] # Your FoodRatings object will be instantiated and called as such: