LeetCode 2353. 设计食物评分系统【设计,哈希表,有序集合;堆+懒删除】1781
本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章中,我不仅会讲解多种解题思路及其优化,还会用多种编程语言实现题解,涉及到通用解法时更将归纳总结出相应的算法模板。
为了方便在PC上运行调试、分享代码文件,我还建立了相关的仓库:https://github.com/memcpy0/LeetCode-Conquest。在这一仓库中,你不仅可以看到LeetCode原题链接、题解代码、题解文章链接、同类题目归纳、通用解法总结等,还可以看到原题出现频率和相关企业等重要信息。如果有其他优选题解,还可以一同分享给他人。
由于本系列文章的内容随时可能发生更新变动,欢迎关注和收藏征服LeetCode系列文章目录一文以作备忘。
设计一个支持下述操作的食物评分系统:
- 修改 系统中列出的某种食物的评分。
- 返回系统中某一类烹饪方式下评分最高的食物。
实现 FoodRatings 类:
FoodRatings(String[] foods, String[] cuisines, int[] ratings)初始化系统。食物由foods、cuisines和ratings描述,长度均为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 之前,也就是说,要么 x 是 y 的前缀,或者在满足 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^4n == foods.length == cuisines.length == ratings.length1 <= foods[i].length, cuisines[i].length <= 10foods[i]、cuisines[i]由小写英文字母组成1 <= ratings[i] <= 108foods中的所有字符串 互不相同- 在对
changeRating的所有调用中,food是系统中食物的名字。 - 在对
highestRated的所有调用中,cuisine是系统中 至少一种 食物的烹饪方式。 - 最多调用
changeRating和highestRated总计2 * 10^4次
解法1 平衡树
我们可以用一个哈希表 f s fs fs 记录每个食物名称、对应的食物评分和烹饪方式,另一个哈希表套平衡树 c s cs cs 记录每个烹饪方式、对应的食物评分和食物名称集合。
对于 changeRating 操作,先从 cs[fs[food].cuisine] 中删除旧数据,然后将 newRating 和 food 记录到 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]
相关文章:
LeetCode 2353. 设计食物评分系统【设计,哈希表,有序集合;堆+懒删除】1781
本文属于「征服LeetCode」系列文章之一,这一系列正式开始于2021/08/12。由于LeetCode上部分题目有锁,本系列将至少持续到刷完所有无锁题之日为止;由于LeetCode还在不断地创建新题,本系列的终止日期可能是永远。在这一系列刷题文章…...
Redis (三)
1、redis复制 简单的概括就是主从复制,master以写为主,Slave以读为主,当master数据发生变化的时候,自动将更新的数据异步同步到其他的slave是数据库。 使用这种机制的话,可以做到读写分离,可以减轻主机负担…...
CompletableFuture超详解与实践
0.背景 一个接口可能需要调用 N 个其他服务的接口,这在项目开发中还是挺常见的。举个例子:用户请求获取订单信息,可能需要调用用户信息、商品详情、物流信息、商品推荐等接口,最后再汇总数据统一返回。 如果是串行(按…...
Maven之私服
1 介绍 团队开发现状分析私服是一台独立的服务器,用于解决团队内部的资源共享与资源同步问题Nexus Sonatype公司的一款maven私服产品 下载地址:https://help.sonatype.com/repomanager3/download win版安装包:https://pan.baidu.com/s/1wk…...
#define宏定义的初探
前言: 最基本的#define定义方式 #define可以定义宏,这点相信大家并不陌生,其定义的方式十分简单,给大家随便来一个最简单、最基础的定义方式看看: #include<stdio.h> #define a 3 int main() { printf(&quo…...
机器学习 -决策树的案例
场景 我们对决策树的基本概念和算法其实已经有过了解,那我们如何利用决策树解决问题呢? 构建决策树 数据准备 我们准备了一些数据如下: # 定义新的数据集 new_dataSet [[晴朗, 是, 高, 是],[雨天, 否, 低, 否],[阴天, 是, 中, 是],[晴朗…...
04、Kafka ------ 各个功能的作用解释(Cluster、集群、Broker、位移主题、复制因子、领导者副本、主题)
目录 启动命令:CMAK的用法★ 在CMAK中添加 Cluster★ 在CMAK中查看指定集群★ 在CMAK中查看 Broker★ 位移主题★ 复制因子★ 领导者副本和追随者副本★ 查看主题 启动命令: 1、启动 zookeeper 服务器端 小黑窗输入命令: zkServer 2、启动 …...
1、C语言:数据类型/运算符与表达式
数据类型/运算符/表达式 1.数据类型与长度2.常量3.声明4. 运算符5. 表达式 1.数据类型与长度 基本数据类型 类型说明char字符型,占用一个字节,可以存放本地字符集中的一个字符int整型,通常反映了所有机器中整数的最自然长度float单精度浮点…...
[ffmpeg系列 03] 文件、流地址(视频)解码为YUV
一 代码 ffmpeg版本5.1.2,dll是:ffmpeg-5.1.2-full_build-shared。x64的。 文件、流地址对使用者来说是一样。 流地址(RTMP、HTTP-FLV、RTSP等):信令完成后,才进行音视频传输。信令包括音视频格式、参数等协商。 接流的在实际…...
python算法每日一练:连续子数组的最大和
这是一道关于动态规划的算法题: 题目描述: 给定一个整数数组 nums,请找出该数组中连续子数组的最大和,并返回这个最大和。 示例: 输入:[-2, 1, -3, 4, -1, 2, 1, -5, 4] 输出:6 解释ÿ…...
一个vue3的tree组件
https://download.csdn.net/download/weixin_41012767/88709466...
新手练习项目 4:简易2048游戏的实现(C++)
名人说:莫听穿林打叶声,何妨吟啸且徐行。—— 苏轼《定风波莫听穿林打叶声》 Code_流苏(CSDN)(一个喜欢古诗词和编程的Coder) 目录 一、效果图二、代码(带注释)三、说明 一、效果图 二、代码(带…...
2023年度总结:技术沉淀、持续学习
2023年度总结:技术沉淀、持续学习 一、引言 今年是我毕业的第二个年头,也是完整的一年,到了做年终总结的时候了 这一年谈了女朋友,学习了不少技术,是充实且美好的一年! 首先先看年初定的小目标…...
Unity 利用UGUI之Slider制作进度条
在Unity中使用Slider和Text组件可以制作简单的进度条。 首先在场景中右键->UI->Slider,新建一个Slider组件: 同样方法新建一个Text组件,最终如图: 创建一个进度模拟脚本,Slider_Progressbar.cs using System.C…...
OCS2 入门教程(四)- 机器人示例
系列文章目录 前言 OCS2 包含多个机器人示例。我们在此简要讨论每个示例的主要特点。 System State Dim. Input Dim. Constrained Caching Double Integrator 2 1 No No Cartpole 4 1 Yes No Ballbot 10 3 No No Quadrotor 12 4 No No Mobile Manipul…...
FreeRTOS学习第6篇–任务状态挂起恢复删除等操作
目录 FreeRTOS学习第6篇--任务状态挂起恢复删除等操作任务的状态设计实验IRReceiver_Task任务相关代码片段实验现象本文中使用的测试工程 FreeRTOS学习第6篇–任务状态挂起恢复删除等操作 本文目标:学习与使用FreeRTOS中的几项操作,有挂起恢复删除等操作…...
BLE Mesh蓝牙组网技术详细解析之Access Layer访问层(六)
目录 一、什么是BLE Mesh Access Layer访问层? 二、Access payload 2.1 Opcode 三、Access layer behavior 3.1 Access layer发送消息的流程 3.2 Access layer接收消息的流程 3.3 Unacknowledged and acknowledged messages 3.3.1 Unacknowledged message …...
Netlink 通信机制
文章目录 前言一、Netlink 介绍二、示例代码参考资料 前言 一、Netlink 介绍 Netlink套接字是用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC) ,也是网络应用程序与内核通信的最常用的接口。 在Linux 内核中,使用netlink 进行应用与内核通信的应用有…...
2024.1.8每日一题
LeetCode 回旋镖的数量 447. 回旋镖的数量 - 力扣(LeetCode) 题目描述 给定平面上 n 对 互不相同 的点 points ,其中 points[i] [xi, yi] 。回旋镖 是由点 (i, j, k) 表示的元组 ,其中 i 和 j 之间的距离和 i 和 k 之间的欧式…...
看了致远OA的表单设计后的思考
更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码: https://gitee.com/nbacheng/ruoyi-nbcio 演示地址:RuoYi-Nbcio后台管理系统 更多nbcio-boot功能请看演示系统 gitee源代码地址 后端代码: https://gitee.com/nbacheng/n…...
利用最小二乘法找圆心和半径
#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …...
深度学习在微纳光子学中的应用
深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向: 逆向设计 通过神经网络快速预测微纳结构的光学响应,替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…...
树莓派超全系列教程文档--(61)树莓派摄像头高级使用方法
树莓派摄像头高级使用方法 配置通过调谐文件来调整相机行为 使用多个摄像头安装 libcam 和 rpicam-apps依赖关系开发包 文章来源: http://raspberry.dns8844.cn/documentation 原文网址 配置 大多数用例自动工作,无需更改相机配置。但是,一…...
mongodb源码分析session执行handleRequest命令find过程
mongo/transport/service_state_machine.cpp已经分析startSession创建ASIOSession过程,并且验证connection是否超过限制ASIOSession和connection是循环接受客户端命令,把数据流转换成Message,状态转变流程是:State::Created 》 St…...
uni-app学习笔记二十二---使用vite.config.js全局导入常用依赖
在前面的练习中,每个页面需要使用ref,onShow等生命周期钩子函数时都需要像下面这样导入 import {onMounted, ref} from "vue" 如果不想每个页面都导入,需要使用node.js命令npm安装unplugin-auto-import npm install unplugin-au…...
oracle与MySQL数据库之间数据同步的技术要点
Oracle与MySQL数据库之间的数据同步是一个涉及多个技术要点的复杂任务。由于Oracle和MySQL的架构差异,它们的数据同步要求既要保持数据的准确性和一致性,又要处理好性能问题。以下是一些主要的技术要点: 数据结构差异 数据类型差异ÿ…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
用鸿蒙HarmonyOS5实现中国象棋小游戏的过程
下面是一个基于鸿蒙OS (HarmonyOS) 的中国象棋小游戏的实现代码。这个实现使用Java语言和鸿蒙的Ability框架。 1. 项目结构 /src/main/java/com/example/chinesechess/├── MainAbilitySlice.java // 主界面逻辑├── ChessView.java // 游戏视图和逻辑├──…...
[USACO23FEB] Bakery S
题目描述 Bessie 开了一家面包店! 在她的面包店里,Bessie 有一个烤箱,可以在 t C t_C tC 的时间内生产一块饼干或在 t M t_M tM 单位时间内生产一块松糕。 ( 1 ≤ t C , t M ≤ 10 9 ) (1 \le t_C,t_M \le 10^9) (1≤tC,tM≤109)。由于空间…...
