LeetCode 2353. 设计食物评分系统【设计,哈希表,有序集合;堆+懒删除】1781

news/2024/11/24 11:26:57/

本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。

为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。

由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。

设计一个支持下述操作的食物评分系统:

  • 修改 系统中列出的某种食物的评分。
  • 返回系统中某一类烹饪方式下评分最高的食物。

实现 FoodRatings 类:

  • FoodRatings(String[] foods, String[] cuisines, int[] ratings) 初始化系统。食物由 foodscuisinesratings 描述,长度均为 n
    • foods[i] 是第 i 种食物的名字。
    • cuisines[i] 是第 i 种食物的烹饪方式。
    • ratings[i] 是第 i 种食物的最初评分。
  • void changeRating(String food, int newRating) 修改名字为 food 的食物的评分。
  • String highestRated(String cuisine) 返回指定烹饪方式 cuisine 下评分最高的食物的名字。如果存在并列,返回 字典序较小 的名字。

注意,字符串 x 的字典序比字符串 y 更小的前提是:x 在字典中出现的位置在 y 之前,也就是说,要么 xy 的前缀,或者在满足 x[i] != y[i] 的第一个位置 i 处,x[i] 在字母表中出现的位置在 y[i] 之前。

示例:
输入

["FoodRatings", "highestRated", "highestRated", "changeRating", "highestRated", "changeRating", "highestRated"]
[[["kimchi", "miso", "sushi", "moussaka", "ramen", "bulgogi"], ["korean", "japanese", "japanese", "greek", "japanese", "korean"], [9, 12, 8, 15, 14, 7]], ["korean"], ["japanese"], ["sushi", 16], ["japanese"], ["ramen", 16], ["japanese"]]

输出

[null, "kimchi", "ramen", null, "sushi", null, "ramen"]

解释

FoodRatings foodRatings = new FoodRatings(["kimchi", "miso", "sushi", "moussaka", "ramen", "bulgogi"], ["korean", "japanese", "japanese", "greek", "japanese", "korean"], [9, 12, 8, 15, 14, 7]);
foodRatings.highestRated("korean"); // 返回 "kimchi"// "kimchi" 是分数最高的韩式料理,评分为 9 。
foodRatings.highestRated("japanese"); // 返回 "ramen"// "ramen" 是分数最高的日式料理,评分为 14 。
foodRatings.changeRating("sushi", 16); // "sushi" 现在评分变更为 16 。
foodRatings.highestRated("japanese"); // 返回 "sushi"// "sushi" 是分数最高的日式料理,评分为 16 。
foodRatings.changeRating("ramen", 16); // "ramen" 现在评分变更为 16 。
foodRatings.highestRated("japanese"); // 返回 "ramen"// "sushi" 和 "ramen" 的评分都是 16 。// 但是,"ramen" 的字典序比 "sushi" 更小。

提示:

  • 1 <= n <= 2 * 10^4
  • n == foods.length == cuisines.length == ratings.length
  • 1 <= foods[i].length, cuisines[i].length <= 10
  • foods[i]cuisines[i] 由小写英文字母组成
  • 1 <= ratings[i] <= 108
  • foods 中的所有字符串 互不相同
  • 在对 changeRating 的所有调用中,food 是系统中食物的名字。
  • 在对 highestRated 的所有调用中,cuisine 是系统中 至少一种 食物的烹饪方式。
  • 最多调用 changeRatinghighestRated 总计 2 * 10^4

解法1 平衡树

我们可以用一个哈希表 f s fs fs 记录每个食物名称、对应的食物评分和烹饪方式,另一个哈希表套平衡树 c s cs cs 记录每个烹饪方式、对应的食物评分和食物名称集合。

对于 changeRating 操作,先从 cs[fs[food].cuisine] 中删除旧数据,然后将 newRatingfood 记录到 c s cs cs f s fs fs 中:

// cpp
class FoodRatings {
private:unordered_map<string, pair<int, string>> fs;unordered_map<string, set<pair<int, string>>> cs;
public:FoodRatings(vector<string>& foods, vector<string>& cuisines, vector<int>& ratings) {for (int i = 0, n = foods.size(); i < n; ++i) {auto &f = foods[i], &c = cuisines[i];int r = ratings[i];fs[f] = {r, c};cs[c].emplace(-r, f);}}void changeRating(string food, int newRating) {auto &[r, c] = fs[food];auto &s = cs[c];s.erase({-r, food}); // 移除旧数据s.emplace(-newRating, food); // 添加新数据r = newRating;}string highestRated(string cuisine) {return cs[cuisine].begin()->second;}
};
// java
class FoodRatings {Map<String, Pair<Integer, String>> fs = new HashMap<>();Map<String, TreeSet<Pair<Integer, String>>> cs = new HashMap<>();public FoodRatings(String[] foods, String[] cuisines, int[] ratings) {for (var i = 0; i < foods.length; ++i) {String f = foods[i], c = cuisines[i];var r = ratings[i];fs.put(f, new Pair<>(r, c));cs.computeIfAbsent(c, k -> new TreeSet<>((a, b) -> !Objects.equals(a.getKey(), b.getKey()) ? b.getKey() - a.getKey() : // 逆序 a.getValue().compareTo(b.getValue()))).add(new Pair<>(r, f));}}public void changeRating(String food, int newRating) {var e = fs.get(food);var s = cs.get(e.getValue());s.remove(new Pair<>(e.getKey(), food)); // 移除旧数据s.add(new Pair<>(newRating, food)); // 添加新数据fs.put(food, new Pair<>(newRating, e.getValue()));}public String highestRated(String cuisine) {return cs.get(cuisine).first().getValue();}
}
// python
from sortedcontainers import SortedSet
class FoodRatings:def __init__(self, foods: List[str], cuisines: List[str], ratings: List[int]):self.fs = {}self.cs = defaultdict(SortedSet)for f, c, r in zip(foods, cuisines, ratings):self.fs[f] = [r, c]self.cs[c].add((-r, f))def changeRating(self, food: str, newRating: int) -> None:r, c = self.fs[food]s = self.cs[c]s.remove((-r, food)) # 移除旧数据s.add((-newRating, food)) # 添加新数据self.fs[food][0] = newRatingdef highestRated(self, cuisine: str) -> str:return self.cs[cuisine][0][1]

解法2 懒删除堆

另一种做法是用堆:

  • 对于 changeRating 操作,直接往 c s cs cs 中记录,不做任何删除操作;
  • 对于 h i g h e s t R a t e d highestRated highestRated 操作,查看堆顶的食物评分是否等于其实际值,若不相同则意味着对应的元素已被替换成了其他值,堆顶存的是个垃圾数据,直接弹出堆顶;否则堆顶就是答案。
// cpp
class FoodRatings {
private:unordered_map<string, pair<int, string>> fs;unordered_map<string, priority_queue<pair<int, string>, vector<pair<int, string>>, greater<>>> cs;
public:FoodRatings(vector<string>& foods, vector<string>& cuisines, vector<int>& ratings) {for (int i = 0, n = foods.size(); i < n; ++i) {auto &f = foods[i], &c = cuisines[i];int r = ratings[i];fs[f] = {r, c};cs[c].emplace(-r, f);}}void changeRating(string food, int newRating) {auto &[r, c] = fs[food];cs[c].emplace(-newRating, food); // 直接添加新数据,后面 highestRated 再删除旧的r = newRating;}string highestRated(string cuisine) {auto &q = cs[cuisine];while (-q.top().first != fs[q.top().second].first) // 堆顶的食物评分不等于其实际值q.pop();return q.top().second;}
};
// java
class FoodRatings {Map<String, Pair<Integer, String>> fs = new HashMap<>();Map<String, Queue<Pair<Integer, String>>> cs = new HashMap<>();public FoodRatings(String[] foods, String[] cuisines, int[] ratings) {for (var i = 0; i < foods.length; ++i) {String f = foods[i], c = cuisines[i];var r = ratings[i];fs.put(f, new Pair<>(r, c));cs.computeIfAbsent(c, k -> new PriorityQueue<>((a, b) -> !Objects.equals(a.getKey(), b.getKey()) ? b.getKey() - a.getKey() : // 逆序 a.getValue().compareTo(b.getValue()))).add(new Pair<>(r, f));}}public void changeRating(String food, int newRating) {var c = fs.get(food).getValue();cs.get(c).offer(new Pair<>(newRating, food)); // 直接添加新数据,后面 highestRated 再删除旧的fs.put(food, new Pair<>(newRating, c)); // 记录新评分}public String highestRated(String cuisine) {var q = cs.get(cuisine);while (!Objects.equals(q.peek().getKey(), fs.get(q.peek().getValue()).getKey()))q.poll();return q.peek().getValue();}
}
// python
class FoodRatings:def __init__(self, foods: List[str], cuisines: List[str], ratings: List[int]):self.fs = {}self.cs = defaultdict(list)for f, c, r in zip(foods, cuisines, ratings):self.fs[f] = [r, c]heappush(self.cs[c], (-r, f))def changeRating(self, food: str, newRating: int) -> None:f = self.fs[food]heappush(self.cs[f[1]], (-newRating, food)) # 直接添加新数据,后面highestRated再删除旧的f[0] = newRatingdef highestRated(self, cuisine: str) -> str:h = self.cs[cuisine]while -h[0][0] != self.fs[h[0][1]][0]: # 堆顶的食物评分!=实际值heappop(h)return h[0][1]

http://www.ppmy.cn/news/1296400.html

相关文章

Java标识符规则解析

在 Java 编程语言中&#xff0c;标识符是用于命名程序元素的名称&#xff0c;如变量、方法、类等。标识符的解析是指在编程过程中如何定义和使用合法的标识符。 以下是关于 Java 标识符解析的一些规则和要点&#xff1a; 标识符可以由字母、数字、下划线(_)和美元符号($)组成&…

代码随想录第四十五天——爬楼梯,零钱兑换,完全平方数

leetcode 70. 爬楼梯 题目链接&#xff1a;爬楼梯 爬楼梯也可以通过完全背包的解法求解。 确定dp数组以及下标的含义 dp[i]&#xff1a;爬到有i个台阶的楼顶&#xff0c;有dp[i]种方法确定递推公式 求装满背包的方法数的递推公式都是dp[i] dp[i - nums[j]] 所以本题的递推公…

ubuntu桥接方式上网

vmvare:VMware Workstation 17 Pro ubuntu: Ubuntu 14.04.6 LTS window10 下面是我的电脑配置 下面是ubuntu虚拟机的配置 vi /etc/network/interfaces 下面的gateway就是window -ipconfig 截图里的默认网关 auto lo iface lo inet loopbackauto eth0 iface eth0 inet stat…

基于YOLOv5全系列参数模型【n/s/m/l/x】开发构建道路交通场景下CCTSDB2021交通标识检测识别系统

交通标志检测是交通标志识别系统中的一项重要任务。与其他国家的交通标志相比&#xff0c;中国的交通标志有其独特的特点。卷积神经网络&#xff08;CNN&#xff09;在计算机视觉任务中取得了突破性进展&#xff0c;在交通标志分类方面取得了巨大的成功。CCTSDB 数据集是由长沙…

读元宇宙改变一切笔记01_起源

1. 元宇宙是我们下一个生存之地 1.1. 1968年&#xff0c;只有不到10%的美国家庭拥有彩色电视&#xff0c;但当年票房排名第二位的电影《2001&#xff1a;太空漫游》&#xff08;2001: A Space Odyssey&#xff09;设想了这样的未来 1.1.1. 斯坦利库布里克(Stanley Kubrick) …

自动驾驶记忆泊车功能规范

目录 1. 文档范围. 5 2. 功能描述. 6 3. 状态机. 8 3.1 状态描述. 8 3.2 状态迁移条件. 9 4. 功能清单. 19 5. 功能流程. 30 5.1 功能逻辑. 30 5.1.1 HU路线列表查看. 30 5.1.2 HU路线学习. 30 5.1.3 HU使用路线匹配. 52…

网络调试 UDP1,开发板用动态地址-入门6

https://www.bilibili.com/video/BV1zx411d7eC?p11&vd_source109fb20ee1f39e5212cd7a443a0286c5 1, 开发板连接路由器 1.1&#xff0c;烧录无OS UDP例程 1.2&#xff0c;Mini USB连接电脑 1.3&#xff0c;开发板LAN接口连接路由器 2. Ping开发板与电脑之间通信* 2.1 根据…

【docker笔记】Docker网络

Docker网络 容器间的互联和通信以及端口映射 容器IP变动时候可以通过服务名直接网络通信而不受到影响 常用命令 查看网络 docker network ls创建网络 docker network create XXX网络名字查看网络源数据 docker network inspect XXX网络名字删除网络 docker network rm…