从这周开始继续打周赛,尝试以赛促练,以后也想专门记录一下每周周赛一些我不会的题目,以及别人好的思路。
这次又只AC了前两题,基本上就是模拟,没有什么好说的。第三题本来一眼看以为是区间修改,线段树,结果一想自己线段树也不会啊,故尝试HashMap
大佬的手速场,我的反思场,哎,算法这条路还是道阻且长啊。
设计一个支持下述操作的食物评分系统:
修改系统中列出的某种食物的评分。
返回系统中某一类烹饪方式下评分最高的食物。 实现 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
数据结构存储同一类烹饪方式的菜,并定义其排序规则,虽然建立红黑树的总时间为O(NlogN)
,但是排序后查询只需要2x104 xO(1),最后同一类的菜用HashMap
存储。
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));
给你一个下标从 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
记录,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;
}
}