1.模拟※(0x3f:从周赛中学算法 2022下)

来自0x3f【从周赛中学算法 - 2022 年周赛题目总结(下篇)】:https://leetcode.cn/circle/discuss/WR1MJP/
模拟指按照题目要求实现相应代码,一般暴力即可通过。
某些模拟题会有点复杂,这也能很好地锻炼实现代码的能力。
常见于周赛第一题(约占 90%)、第二题(约占 40%)和第三题(约占 19%)。

题目难度
2437. 有效时间的数目1427
2349. 设计数字容器系统1540
2456. 最流行的视频创作者1548
2409. 统计共同度过的日子数1562
2512. 奖励最顶尖的 K 名学生1636
2502. 设计内存分配器1746
🔺2353. 设计食物评分系统1782

灵神-从周赛中学算法(模拟)

2437. 有效时间的数目

难度简单14

给你一个长度为 5 的字符串 time ,表示一个电子时钟当前的时间,格式为 "hh:mm"最早 可能的时间是 "00:00"最晚 可能的时间是 "23:59"

在字符串 time 中,被字符 ? 替换掉的数位是 未知的 ,被替换的数字可能是 09 中的任何一个。

请你返回一个整数 answer ,将每一个 ? 都用 09 中一个数字替换后,可以得到的有效时间的数目。

示例 1:

输入:time = "?5:00"
输出:2
解释:我们可以将 ? 替换成 0 或 1 ,得到 "05:00" 或者 "15:00" 。注意我们不能替换成 2 ,因为时间 "25:00" 是无效时间。所以我们有两个选择。

示例 2:

输入:time = "0?:0?"
输出:100
解释:两个 ? 都可以被 0 到 9 之间的任意数字替换,所以我们总共有 100 种选择。

示例 3:

输入:time = "??:??"
输出:1440
解释:小时总共有 24 种选择,分钟总共有 60 种选择。所以总共有 24 * 60 = 1440 种选择。

提示:

  • time 是一个长度为 5 的有效字符串,格式为 "hh:mm"
  • "00" <= hh <= "23"
  • "00" <= mm <= "59"
  • 字符串中有的数位是 '?' ,需要用 09 之间的数字替换。

方法一:模拟,还是wa了两次(注意没有24点几几)

class Solution {
    public int countTime(String time) {
        String[] s = time.split(":");
        int res = 1;
        String hour = s[0];
        if(hour.charAt(0) == '?' && hour.charAt(1) == '?') res *= 24;
        else if(hour.charAt(0) != '?' && hour.charAt(1) != '?') res *= 1;
        else if(hour.charAt(0) == '?'){
            if(hour.charAt(1)-'0' < 4) res *= 3;
            else res *= 2;
        } else{ // hour.charAt(1) == '?'
            if(hour.charAt(0)-'0' <= 1) res *= 10;
            else res *= 4;
        }
        String minute = s[1];
        if(minute.charAt(0) == '?' && minute.charAt(1) == '?') res *= 60;
        else if(minute.charAt(0) != '?' && minute.charAt(1) != '?') res *= 1;
        else if(minute.charAt(0) == '?'){
            res *= 6;
        } else{
            res *= 10;
        }
        return res;
    }
}

方法二:反向思考

反向思考,不用if else 判断情况,而是枚举[0,period]每位数字,判断time能否组成

class Solution {
    public int countTime(String time) {
        return count(time.substring(0, 2), 24) * count(time.substring(3), 60);
    }

    // 反向思考,不用if else 判断情况,而是枚举[0,period]每位数字,判断time能否组成
    public int count(String time, int period){
        char[] s = time.toCharArray();
        int ans = 0;
        for(int i = 0; i < period; i++){
            if((s[0] == '?' || i / 10 == s[0] - '0') &&
                (s[1] == '?' || i % 10 == s[1] - '0'))
                ans += 1;
        }
        return ans;
    }

}

🎃2349. 设计数字容器系统

难度中等22

设计一个数字容器系统,可以实现以下功能:

  • 在系统中给定下标处 插入 或者 替换 一个数字。
  • 返回 系统中给定数字的最小下标。

请你实现一个 NumberContainers 类:

  • NumberContainers() 初始化数字容器系统。
  • void change(int index, int number) 在下标 index 处填入 number 。如果该下标 index 处已经有数字了,那么用 number 替换该数字。
  • int find(int number) 返回给定数字 number 在系统中的最小下标。如果系统中没有 number ,那么返回 -1

示例:

输入:
["NumberContainers", "find", "change", "change", "change", "change", "find", "change", "find"]
[[], [10], [2, 10], [1, 10], [3, 10], [5, 10], [10], [1, 20], [10]]
输出:
[null, -1, null, null, null, null, 1, null, 2]

解释:
NumberContainers nc = new NumberContainers();
nc.find(10); // 没有数字 10 ,所以返回 -1 。
nc.change(2, 10); // 容器中下标为 2 处填入数字 10 。
nc.change(1, 10); // 容器中下标为 1 处填入数字 10 。
nc.change(3, 10); // 容器中下标为 3 处填入数字 10 。
nc.change(5, 10); // 容器中下标为 5 处填入数字 10 。
nc.find(10); // 数字 10 所在的下标为 1 ,2 ,3 和 5 。因为最小下标为 1 ,所以返回 1 。
nc.change(1, 20); // 容器中下标为 1 处填入数字 20 。注意,下标 1 处之前为 10 ,现在被替换为 20 。
nc.find(10); // 数字 10 所在下标为 2 ,3 和 5 。最小下标为 2 ,所以返回 2 。

提示:

  • 1 <= index, number <= 109
  • 调用 changefind总次数 不超过 105 次。
class NumberContainers {

    Map<Integer, TreeSet<Integer>> num_idxs; //key为对应数字,val为索引列表
    Map<Integer, Integer> idx_num;//key为索引,val为数字

    public NumberContainers() {
        num_idxs = new HashMap<>();
        idx_num = new HashMap<>();
    }
    
    public void change(int index, int num) {
        if(!idx_num.containsKey(index)){
            idx_num.put(index, num);
        }else{
            int t = idx_num.get(index);
            idx_num.put(index, num);
            num_idxs.get(t).remove(index);
            if(num_idxs.get(t).size() == 0) num_idxs.remove(t);
        }
        if(!num_idxs.containsKey(num)){
            num_idxs.put(num, new TreeSet<>());
        }
        num_idxs.get(num).add(index);
    }
    
    public int find(int num) {
        return num_idxs.containsKey(num) ? num_idxs.get(num).first() : -1;
    }
}

2456. 最流行的视频创作者

难度中等19

给你两个字符串数组 creatorsids ,和一个整数数组 views ,所有数组的长度都是 n 。平台上第 i 个视频者是 creator[i] ,视频分配的 id 是 ids[i] ,且播放量为 views[i]

视频创作者的 流行度 是该创作者的 所有 视频的播放量的 总和 。请找出流行度 最高 创作者以及该创作者播放量 最大 的视频的 id 。

  • 如果存在多个创作者流行度都最高,则需要找出所有符合条件的创作者。
  • 如果某个创作者存在多个播放量最高的视频,则只需要找出字典序最小的 id

返回一个二维字符串数组 answer ,其中 answer[i] = [creatori, idi] 表示 creatori 的流行度 最高 且其最流行的视频 id 是 idi ,可以按任何顺序返回该结果*。*

示例 1:

输入:creators = ["alice","bob","alice","chris"], ids = ["one","two","three","four"], views = [5,10,5,4]
输出:[["alice","one"],["bob","two"]]
解释:
alice 的流行度是 5 + 5 = 10 。
bob 的流行度是 10 。
chris 的流行度是 4 。
alice 和 bob 是流行度最高的创作者。
bob 播放量最高的视频 id 为 "two" 。
alice 播放量最高的视频 id 是 "one" 和 "three" 。由于 "one" 的字典序比 "three" 更小,所以结果中返回的 id 是 "one" 。

示例 2:

输入:creators = ["alice","alice","alice"], ids = ["a","b","c"], views = [1,2,2]
输出:[["alice","b"]]
解释:
id 为 "b" 和 "c" 的视频都满足播放量最高的条件。
由于 "b" 的字典序比 "c" 更小,所以结果中返回的 id 是 "b" 。

提示:

  • n == creators.length == ids.length == views.length
  • 1 <= n <= 105
  • 1 <= creators[i].length, ids[i].length <= 5
  • creators[i]ids[i] 仅由小写英文字母组成
  • 0 <= views[i] <= 105

双哈希模拟:①创作者;总流行度 ②创作者; 视频数[视频下标,视频播放量]

  • !!!在这种int累加的情况下,要考虑int溢出问题
class Solution {
    public List<List<String>> mostPopularCreator(String[] creators, String[] ids, int[] views) {
        Map<String, Double> creat_popu = new HashMap<>(); // 创作者;流行度
        double maxpopu = 0; // 最大流行度
        Map<String, List<int[]>> creat_ids = new HashMap<>(); // 创作者; 视频数[视频下标,视频播放量]
        for(int i = 0; i < creators.length; i++){
            String creat = creators[i];
            double v = (double)views[i];
            creat_popu.put(creat, creat_popu.getOrDefault(creat, 0.0) + v);
            maxpopu = Math.max(maxpopu, creat_popu.get(creat));
            List<int[]> tmp = creat_ids.getOrDefault(creat, new ArrayList<>());
            tmp.add(new int[]{i, (int)v});
            creat_ids.put(creat, tmp);
        }
        List<List<String>> res = new ArrayList<>();
        for(Map.Entry<String, Double> entry : creat_popu.entrySet()){
            if(entry.getValue() == maxpopu) {
                List<int[]> list = creat_ids.get(entry.getKey());
                // 按照播放量从大到小排序,相同的话则比较视频的字典序,注意这里用ids数组来对下标进行排序
                Collections.sort(list, (a, b) -> a[1] == b[1] ? ids[a[0]].compareTo(ids[b[0]]) : b[1] - a[1]);
                List<String> cur = new ArrayList<>();
                cur.add(entry.getKey());
                cur.add(ids[list.get(0)[0]]);
                res.add(cur);
            }
        }
        return res;
    }
}

2409. 统计共同度过的日子数(区间求交集问题)

难度简单9

Alice 和 Bob 计划分别去罗马开会。

给你四个字符串 arriveAliceleaveAlicearriveBobleaveBob 。Alice 会在日期 arriveAliceleaveAlice 之间在城市里(日期为闭区间),而 Bob 在日期 arriveBobleaveBob 之间在城市里(日期为闭区间)。每个字符串都包含 5 个字符,格式为 "MM-DD" ,对应着一个日期的月和日。

请你返回 Alice和 Bob 同时在罗马的天数。

你可以假设所有日期都在 同一个 自然年,而且 不是 闰年。每个月份的天数分别为:[31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]

示例 1:

输入:arriveAlice = "08-15", leaveAlice = "08-18", arriveBob = "08-16", leaveBob = "08-19"
输出:3
解释:Alice 从 8 月 15 号到 8 月 18 号在罗马。Bob 从 8 月 16 号到 8 月 19 号在罗马,他们同时在罗马的日期为 8 月 16、17 和 18 号。所以答案为 3 。

示例 2:

输入:arriveAlice = "10-01", leaveAlice = "10-31", arriveBob = "11-01", leaveBob = "12-31"
输出:0
解释:Alice 和 Bob 没有同时在罗马的日子,所以我们返回 0 。

提示:

  • 所有日期的格式均为 "MM-DD"
  • Alice 和 Bob 的到达日期都 早于或等于 他们的离开日期。
  • 题目测试用例所给出的日期均为 非闰年 的有效日期。

区间求交集问题:max(min(b,d)-max(a,c)+1,0)

Alice的到达的时间就相当于a,它的离开时间就是b。同理,Bob的到达时间就是c,离开时间就是d。一开始我们不知道abcd谁大谁小,但是我们肯定是求[a,b]与[c,d]之间的交集。我们可以把具体日期变成一个数字表示它的长度,这里我们就把日期变成今年到这天共经历了多少天。四个日期都变成纯数字后,我们就可以用一个公式来求交集:max{0,min{b,d}-max{a,c}}

class Solution {
    private static final int[] month = new int[]{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
    public int countDaysTogether(String arriveAlice, String leaveAlice, String arriveBob, String leaveBob) {
        int aa = get(arriveAlice);
        int al = get(leaveAlice);
        int ba = get(arriveBob);
        int bl = get(leaveBob);
        return Math.max(Math.min(al, bl) - Math.max(aa, ba) + 1, 0);
    }

    public int get(String time){
        int res = 0;
        String[] s = time.split("-");
        for(int i = 0; i < Integer.parseInt(s[0])-1; i++){
            res += month[i];
        }
        res += Integer.parseInt(s[1]);
        return res;
    }
}

纯模拟:

class Solution {
    private static final int[] month = new int[]{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

    public int countDaysTogether(String arriveAlice, String leaveAlice, String arriveBob, String leaveBob) {
        int Alicearr = countday(arriveAlice);
        int Alicelea = countday(leaveAlice);
        int Bobarr = countday(arriveBob);
        int Boblea = countday(leaveBob);
        // 如果 A离开时间 < B到达时间,则说明没有交集
        if(Alicelea < Bobarr || Boblea < Alicearr) return 0;
        // A到达时间小于B到达时间(A来的早)
        if(Alicearr < Bobarr){
            int m = Math.min(Alicelea, Boblea);
            // AB早离开的时间 - 晚到的人的天数
            return m - Bobarr + 1;
        }else{
            int m = Math.min(Alicelea, Boblea);
            return m - Alicearr + 1;
        }
    }

    public int countday(String time){
        int res = 0;
        String[] s = time.split("-");
        for(int i = 0; i < Integer.parseInt(s[0])-1; i++){
            res += month[i];
        }
        res += Integer.parseInt(s[1]);
        return res;
    }
}
2446. 判断两个事件是否存在冲突

难度简单17

给你两个字符串数组 event1event2 ,表示发生在同一天的两个闭区间时间段事件,其中:

  • event1 = [startTime1, endTime1]
  • event2 = [startTime2, endTime2]

事件的时间为有效的 24 小时制且按 HH:MM 格式给出。

当两个事件存在某个非空的交集时(即,某些时刻是两个事件都包含的),则认为出现 冲突

如果两个事件之间存在冲突,返回 true ;否则,返回 false

示例 1:

输入:event1 = ["01:15","02:00"], event2 = ["02:00","03:00"]
输出:true
解释:两个事件在 2:00 出现交集。

示例 2:

输入:event1 = ["01:00","02:00"], event2 = ["01:20","03:00"]
输出:true
解释:两个事件的交集从 01:20 开始,到 02:00 结束。

示例 3:

输入:event1 = ["10:00","11:00"], event2 = ["14:00","15:00"]
输出:false
解释:两个事件不存在交集。

提示:

  • evnet1.length == event2.length == 2.
  • event1[i].length == event2[i].length == 5
  • startTime1 <= endTime1
  • startTime2 <= endTime2
  • 所有事件的时间都按照 HH:MM 格式给出

class Solution {
    // 区间求交集问题:max(min(b,d)-max(a,c)+1,0)
    public boolean haveConflict(String[] event1, String[] event2) {
        int[] e1 = change(event1);
        int[] e2 = change(event2);
        int res = Math.max(Math.min(e1[1], e2[1]) - Math.max(e1[0], e2[0]) + 1, 0);
        if(res == 0) return false;
        return true;
    }

    public int[] change(String[] e){
        int[] res = new int[2];
        for(int i = 0; i < e.length; i++){
            String[] s = e[i].split(":");
            int hour = Integer.valueOf(s[0]);
            int min = Integer.valueOf(s[1]);
            int cnt = 0;
            while(hour-- > 0){
                cnt += 60;
            }
            cnt += min;
            res[i] = cnt;
        } 
        return res;
    }
}

一行写法:

https://leetcode.cn/problems/determine-if-two-events-have-conflict/solution/shi-jian-duan-zhong-die-de-pan-duan-yi-t-zfh9/

在这里插入图片描述

class Solution {
    public boolean haveConflict(String[] event1, String[] event2) {
        // s1 e1 s2 e2 ; s1 < e2 and s2 < e1
        return event1[0].compareTo(event2[1]) <= 0 && event2[0].compareTo(event1[1]) <= 0;
    }
}

python

class Solution(object):
    def haveConflict(self, event1, event2):
        return event1[0] <= event2[1] and event2[0] <= event1[1]

2512. 奖励最顶尖的 K 名学生

难度中等2

给你两个字符串数组 positive_feedbacknegative_feedback ,分别包含表示正面的和负面的词汇。不会 有单词同时是正面的和负面的。

一开始,每位学生分数为 0 。每个正面的单词会给学生的分数 3 分,每个负面的词会给学生的分数 1 分。

给你 n 个学生的评语,用一个下标从 0 开始的字符串数组 report 和一个下标从 0 开始的整数数组 student_id 表示,其中 student_id[i] 表示这名学生的 ID ,这名学生的评语是 report[i] 。每名学生的 ID 互不相同

给你一个整数 k ,请你返回按照得分 从高到低 最顶尖的 k 名学生。如果有多名学生分数相同,ID 越小排名越前。

示例 1:

输入:positive_feedback = ["smart","brilliant","studious"], negative_feedback = ["not"], report = ["this student is studious","the student is smart"], student_id = [1,2], k = 2
输出:[1,2]
解释:
两名学生都有 1 个正面词汇,都得到 3 分,学生 1 的 ID 更小所以排名更前。

示例 2:

输入:positive_feedback = ["smart","brilliant","studious"], negative_feedback = ["not"], report = ["this student is not studious","the student is smart"], student_id = [1,2], k = 2
输出:[2,1]
解释:
- ID 为 1 的学生有 1 个正面词汇和 1 个负面词汇,所以得分为 3-1=2 分。
- ID 为 2 的学生有 1 个正面词汇,得分为 3 分。
学生 2 分数更高,所以返回 [2,1] 。

提示:

  • 1 <= positive_feedback.length, negative_feedback.length <= 104
  • 1 <= positive_feedback[i].length, negative_feedback[j].length <= 100
  • positive_feedback[i]negative_feedback[j] 都只包含小写英文字母。
  • positive_feedbacknegative_feedback 中不会有相同单词。
  • n == report.length == student_id.length
  • 1 <= n <= 104
  • report[i] 只包含小写英文字母和空格 ' '
  • report[i] 中连续单词之间有单个空格隔开。
  • 1 <= report[i].length <= 100
  • 1 <= student_id[i] <= 109
  • student_id[i] 的值 互不相同
  • 1 <= k <= n
class Solution {
    // TOP-K : 求从高到低,大根堆
    public List<Integer> topStudents(String[] positive_feedback, String[] negative_feedback, String[] report, int[] student_id, int k) {
        Set<String> pos = new HashSet<>();
        Set<String> neg = new HashSet<>();
        for(String pf : positive_feedback){
            pos.add(pf);
        }
        for(String nf : negative_feedback){
            neg.add(nf);
        }
        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] == b[0] ? a[1] - b[1] : b[0] - a[0]);
        for(int i = 0; i < report.length; i++){
            String[] rep = report[i].split(" ");
            int score = 0;
            for(String s : rep){
                if(pos.contains(s)) score += 3;
                if(neg.contains(s)) score -= 1;
            }
            pq.add(new int[]{score, student_id[i]});
        }
        List<Integer> list = new ArrayList<>();
        for(int i = 0; i < k; i++){
            list.add(pq.poll()[1]);
        }
        return list;
    }
}

2502. 设计内存分配器

难度中等14

给你一个整数 n ,表示下标从 0 开始的内存数组的大小。所有内存单元开始都是空闲的。

请你设计一个具备以下功能的内存分配器:

  1. 分配 一块大小为 size 的连续空闲内存单元并赋 id mID
  2. 释放 给定 id mID 对应的所有内存单元。

注意:

  • 多个块可以被分配到同一个 mID
  • 你必须释放 mID 对应的所有内存单元,即便这些内存单元被分配在不同的块中。

实现 Allocator 类:

  • Allocator(int n) 使用一个大小为 n 的内存数组初始化 Allocator 对象。
  • int allocate(int size, int mID) 找出大小为 size 个连续空闲内存单元且位于 最左侧 的块,分配并赋 id mID 。返回块的第一个下标。如果不存在这样的块,返回 -1
  • int free(int mID) 释放 id mID 对应的所有内存单元。返回释放的内存单元数目。

示例:

输入
["Allocator", "allocate", "allocate", "allocate", "free", "allocate", "allocate", "allocate", "free", "allocate", "free"]
[[10], [1, 1], [1, 2], [1, 3], [2], [3, 4], [1, 1], [1, 1], [1], [10, 2], [7]]
输出
[null, 0, 1, 2, 1, 3, 1, 6, 3, -1, 0]

解释
Allocator loc = new Allocator(10); // 初始化一个大小为 10 的内存数组,所有内存单元都是空闲的。
loc.allocate(1, 1); // 最左侧的块的第一个下标是 0 。内存数组变为 [1, , , , , , , , , ]。返回 0 。
loc.allocate(1, 2); // 最左侧的块的第一个下标是 1 。内存数组变为 [1,2, , , , , , , , ]。返回 1 。
loc.allocate(1, 3); // 最左侧的块的第一个下标是 2 。内存数组变为 [1,2,3, , , , , , , ]。返回 2 。
loc.free(2); // 释放 mID 为 2 的所有内存单元。内存数组变为 [1, ,3, , , , , , , ] 。返回 1 ,因为只有 1 个 mID 为 2 的内存单元。
loc.allocate(3, 4); // 最左侧的块的第一个下标是 3 。内存数组变为 [1, ,3,4,4,4, , , , ]。返回 3 。
loc.allocate(1, 1); // 最左侧的块的第一个下标是 1 。内存数组变为 [1,1,3,4,4,4, , , , ]。返回 1 。
loc.allocate(1, 1); // 最左侧的块的第一个下标是 6 。内存数组变为 [1,1,3,4,4,4,1, , , ]。返回 6 。
loc.free(1); // 释放 mID 为 1 的所有内存单元。内存数组变为 [ , ,3,4,4,4, , , , ] 。返回 3 ,因为有 3 个 mID 为 1 的内存单元。
loc.allocate(10, 2); // 无法找出长度为 10 个连续空闲内存单元的空闲块,所有返回 -1 。
loc.free(7); // 释放 mID 为 7 的所有内存单元。内存数组保持原状,因为不存在 mID 为 7 的内存单元。返回 0 。

提示:

  • 1 <= n, size, mID <= 1000
  • 最多调用 allocatefree 方法 1000
class Allocator {

    int[] nums;
    int n;

    public Allocator(int n) {
        this.n = n;
        nums = new int[n];
    }
    
    public int allocate(int size, int mID) {
        // 找到大小 >= size 的连续空闲块
        int left = 0, right = 0;
        while(right < n && right - left < size){
            if(nums[right] != 0){
                left = right + 1;
            }
            right++;
        }
        if(right - left < size) return -1;
        for(int i = left; i < right; i++){
            nums[i] = mID;
        }
        return left;
    }
    
    public int free(int mID) {
        int res = 0;
        for(int i = 0; i < n; i++){
            if(nums[i] == mID){
                res++;
                nums[i] = 0;
            }
        }
        return res;
    }
}

/**
 * Your Allocator object will be instantiated and called as such:
 * Allocator obj = new Allocator(n);
 * int param_1 = obj.allocate(size,mID);
 * int param_2 = obj.free(mID);
 */

🎃2353. 设计食物评分系统

难度中等29

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

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

实现 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 之前,也就是说,要么 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 * 104
  • 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 * 104

Map<String, Pair<Integer, String>>:记录每个食物名称对应食物评分和烹饪方式

Map<String, TreeSet<Pair<Integer, String>>>:记录每个烹饪对应的食物评分和食物名字集合

法一:哈希表套平衡树

  • 在暴力的基础上对料理对应食物评分排序使用TreeSet保存,通过TreeSet.first()可以获得第一个元素
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(int i = 0; i < foods.length; i++){
            String f = foods[i], c = cuisines[i];
            int r = ratings[i];
            fs.put(f, new Pair<>(r, c));
            // JDK1.8以后:computeIfAbsent 相当于GetOrDefault的简写
            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) {
        // 先在cs中删掉旧数据,然后将新排名和food加入到cs和fs中
        Pair<Integer, String> e = fs.get(food);
        TreeSet<Pair<Integer, String>> 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) {
        // TreeSet.first() : 返回TreeSet中当前的第一个(最低)元素
        // lower、floor、ceiling 和 higher 分别返回小于、小于等于、大于等于、大于给定元素的元素,如果不存在这样的元素,则返回 null。
        return cs.get(cuisine).first().getValue();
    }
}

法二:懒删除堆

  • 添加元素时直接加入优先队列,访问元素时,弹出堆顶食物评分与实际评分不相等的值
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(int i = 0; i < foods.length; i++){
            String f = foods[i], c = cuisines[i];
            int r = ratings[i];
            fs.put(f, new Pair<>(r, c));
            // JDK1.8以后:computeIfAbsent 相当于GetOrDefault的简写
            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) {
        // 先在cs中删掉旧数据,然后将新排名和food加入到cs和fs中
        Pair<Integer, String> e = fs.get(food);
        cs.get(e.getValue()).offer(new Pair<>(newRating, food));// 直接添加新数据,后面 highestRated 再删除旧的
        fs.put(food, new Pair<>(newRating, e.getValue())); // 更新评分
    }
    
    public String highestRated(String cuisine) {
        Queue<Pair<Integer, String>> q = cs.get(cuisine);
        // 堆顶的食物评分不等于其实际值,弹出
        while (!Objects.equals(q.peek().getKey(), fs.get(q.peek().getValue()).getKey())) 
            q.poll();
        return q.peek().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);
 */

暴力模拟,超时:

class FoodRatings {
    Map<String, String> food_cui = new HashMap<>(); // food, cui
    Map<String, Integer> food_rating = new HashMap<>(); // food, rating
    Map<String, List<int[]>> cui_food = new HashMap<>(); // cuisines, food[id,ranting]
    String[] foods;

    public FoodRatings(String[] foods, String[] cuisines, int[] ratings) {
        this.foods = foods;
        for(int i = 0; i < foods.length; i++){
            String food = foods[i], cui = cuisines[i];
            int rat = ratings[i];
            food_rating.put(food, rat);
            food_cui.put(food, cui);
            List<int[]> tmp = cui_food.getOrDefault(cui, new ArrayList<>());
            tmp.add(new int[]{i, rat});
            Collections.sort(tmp, (a, b) -> a[1] == b[1] ? foods[a[0]].compareTo(foods[b[0]]) : b[1] - a[1]);
            cui_food.put(cui, tmp);
        }
    }
    
    public void changeRating(String food, int newRating) {
        int oldrat = food_rating.get(food);
        food_rating.put(food, newRating);
        List<int[]> cur = cui_food.get(food_cui.get(food));
        for(int i = 0; i < cur.size(); i++){
            int idx = cur.get(i)[0];
            if(foods[idx].equals(food)){
                cur.remove(i);
                cur.add(new int[]{idx, newRating});
                break;
            } 
        }
        Collections.sort(cur, (a, b) -> a[1] == b[1] ? foods[a[0]].compareTo(foods[b[0]]) : b[1] - a[1]);
        cui_food.put(food_cui.get(food), cur);
        return;
    }
    
    public String highestRated(String cuisine) {
        List<int[]> cur = cui_food.get(cuisine);
        return foods[cur.get(0)[0]];
    }
}

/**
 * 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);
 */
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值