以赛促练-力扣第303场周赛反思

从这周开始继续打周赛,尝试以赛促练,以后也想专门记录一下每周周赛一些我不会的题目,以及别人好的思路。

这次又只AC了前两题,基本上就是模拟,没有什么好说的。第三题本来一眼看以为是区间修改,线段树,结果一想自己线段树也不会啊,故尝试HashMap<String,HashMap<String,Integer>>模拟,结果TLE,过了72/77,一检查确2x104xN确实超了,时间也没了;第四题一眼看联想到了以前做的字典树,结果和优质数对的数量也没啥关系啊,先排序然后尝试在树上再二分这个做法也没法去实现。总而言之,还是做的题目类型太少,容器使用也不熟练。

大佬的手速场,我的反思场,哎,算法这条路还是道阻且长啊。

T3.6126.设计食物评分系统

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

修改系统中列出的某种食物的评分。
返回系统中某一类烹饪方式下评分最高的食物。 实现 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 * 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 是系统中 至少一种 食物的烹饪方式。
最多调用 changeRating 和 highestRated 总计 2 * 104

之前我的模拟中,String highestRated(String cuisine)是遍历所有菜名,找到烹饪方式和cuisine相同的菜进行比较,2x104xN存在超时。这里我们进行优化,由于需要返回一种烹饪方式中rating最高的菜的名字,可以采用类似TreeMap<Food>数据结构存储同一类烹饪方式的菜,并定义其排序规则,虽然建立红黑树的总时间为O(NlogN),但是排序后查询只需要2x104 xO(1),最后同一类的菜用HashMap<String,TreeMap<Food>>存储。

class FoodRatings {
    public class Food{
        String foodname;
        String cuisine;
        int rating;
        public Food(String _foodname, String _cuisine, int _rating){
            this.foodname=_foodname;
            this.cuisine=_cuisine;
            this.rating=_rating;
        }
    }
    //烹饪方式和food的映射,用于String highestRated(String cuisine)
    HashMap<String,TreeSet<Food>> map;
    //菜名和food的映射,用于void changeRating(String food, int newRating)
    HashMap<String,Food> objMap;
    public FoodRatings(String[] foods, String[] cuisines, int[] ratings) {
        int len=foods.length;
        map=new HashMap<>();
        objMap=new HashMap<>();
        for(int i=0;i<len;i++){
            Food food=new Food(foods[i],cuisines[i],ratings[i]);
            TreeSet<Food> cuiSet=map.getOrDefault(cuisines[i],new TreeSet<>(
                (o1,o2)->{
                    if(o1.rating==o2.rating){
                        //字典序升序,o1->o2小的减去大的
                        return o1.foodname.compareTo(o2.foodname);
                    }else{
                        return o2.rating-o1.rating;
                    }
                }
            ));
            cuiSet.add(food);
            map.put(cuisines[i],cuiSet);
            objMap.put(foods[i],food);
        }
    }
    
    public void changeRating(String foodname, int newRating) {
        Food foodObj=objMap.get(foodname);
        String _cuisine=foodObj.cuisine;
        Food newFood=new Food(foodname,_cuisine,newRating);
        TreeSet<Food> cuiSet=map.get(_cuisine);
        //原先的TreeSet中删除旧节点
        cuiSet.remove(foodObj);
        cuiSet.add(newFood);
        map.put(_cuisine,cuiSet);
        objMap.put(foodname,newFood);
    }
    
    public String highestRated(String cuisine) {
        TreeSet<Food> cuiSet=map.get(cuisine);
        Food food=cuiSet.first();
        return food.foodname;
    }
}

/**
 * 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);
 */

其中用lambda表达式建立TreeSet的代码值得学习,其中排序规则多行定义,需要(o1,o2)->{ }有括号,且其中排序语句末尾不需要分号;

TreeSet<Food> cuiSet=map.getOrDefault(cuisines[i],new TreeSet<>(
                (o1,o2)->{
                    if(o1.rating==o2.rating){
                        //字典序升序,o1->o2可以理解为一直是排序后小的减去大的那个表达式
                        return o1.foodname.compareTo(o2.foodname);
                    }else{
                        return o2.rating-o1.rating;
                    }
                }
            ));

用lambda表达式建立PriorityQueue的代码同样值得学习,其中规则只有一行定义,(o1,o2)->不需要括号,且其中排序语句末尾不需要分号;

PriorityQueue<Integer> que=new PriorityQueue<>((o1,o2)->Math.abs(o1)-Math.abs(o2));
T4. 6127.优质数对的数目

给你一个下标从 0 开始的正整数数组 nums 和一个正整数 k 。

如果满足下述条件,则数对 (num1, num2) 是 优质数对 :

num1 和 num2 都 在数组 nums 中存在。 num1 OR num2 和 num1 AND num2 的二进制表示中值为 1
的位数之和大于等于 k ,其中 OR 是按位 或 操作,而 AND 是按位 与 操作。 返回 不同 优质数对的数目。

如果 a != c 或者 b != d ,则认为 (a, b) 和 (c, d) 是不同的两个数对。例如,(1, 2) 和 (2, 1)
不同。

注意:如果 num1 在数组中至少出现 一次 ,则满足 num1 == num2 的数对 (num1, num2) 也可以是优质数对。

示例 1:

输入:nums = [1,2,3,1], k = 3 输出:5 解释:有如下几个优质数对:

  • (3, 3):(3 AND 3) 和 (3 OR 3) 的二进制表示都等于 (11) 。值为 1 的位数和等于 2 + 2 = 4 ,大于等于 k = 3 。
  • (2, 3) 和 (3, 2): (2 AND 3) 的二进制表示等于 (10) ,(2 OR 3) 的二进制表示等于 (11) 。值为 1 的位数和等于 1 + 2 = 3 。
  • (1, 3) 和 (3, 1): (1 AND 3) 的二进制表示等于 (01) ,(1 OR 3) 的二进制表示等于 (11) 。值为 1 的位数和等于 1 + 2 = 3 。 所以优质数对的数目是 5 。

示例 2:

输入:nums = [5,1,1], k = 10 输出:0 解释:该数组中不存在优质数对。

提示:

1 <= nums.length <= 105
1 <= nums[i] <= 109
1 <= k <= 60

这题参考题解灵神的题解。意识到 A or B + A and B是A、B两数的二进制中1的和是关键,因此可以先分别统计nums中所有数字二进制1的个数,在统计之前,观察到数对可以交换顺序,因此考虑去重。在遍历nums的过程中,用HashMap<Integer,Integer> cnt记录,key为nums[i]二进制1的个数,不超过30个,Value记录不同nums[i]的个数。两次循环遍历cnt,根据位数之和>=k,最后累加产生优质数对的数目。

//time:O(N)
class Solution {
    public long countExcellentPairs(int[] nums, int k) {
        long ans=0L;
        HashSet<Integer> vis=new HashSet<>();
        HashMap<Integer,Integer> cnt=new HashMap<>();
        for(int i:nums){
            //去重的过程中统计二进制中1的位数
            if(!vis.contains(i)){
                vis.add(i);
                int bitnum=Integer.bitCount(i);
                /**getOrDefault位置不能随意互换 */
                cnt.put(bitnum,cnt.getOrDefault(bitnum,0)+1);
            }
        }

        for(Map.Entry<Integer,Integer> i:cnt.entrySet()){
            for(Map.Entry<Integer,Integer> j:cnt.entrySet()){
                if(i.getKey()+j.getKey()>=k){
                    ans+=(long)i.getValue()*j.getValue();
                }
            }
        }
        return ans;
    }   
}
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

互联网民工蒋大钊

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值