[Leetcode Weekly Contest]303
链接:LeetCode
[Leetcode]2351. 第一个出现两次的字母
给你一个由小写英文字母组成的字符串 s ,请你找出并返回第一个出现 两次 的字母。
注意:
- 如果 a 的 第二次 出现比 b 的 第二次 出现在字符串中的位置更靠前,则认为字母 a 在字母 b 之前出现两次。
- s 包含至少一个出现两次的字母。
模拟。
class Solution {
public char repeatedCharacter(String s) {
HashSet<Character> set = new HashSet<>();
for(var ch:s.toCharArray()) {
if(set.contains(ch)) return ch;
else set.add(ch);
}
return ' ';
}
}
[Leetcode]2352. 相等行列对
给你一个下标从 0 开始、大小为 n x n 的整数矩阵 grid ,返回满足 Ri 行和 Cj 列相等的行列对 (Ri, Cj) 的数目。
如果行和列以相同的顺序包含相同的元素(即相等的数组),则认为二者是相等的。
用哈希表统计每行出现的次数,然后遍历列,累加哈希表中列出现的次数。
class Solution {
public int equalPairs(int[][] grid) {
int res = 0;
int n = grid.length, m = grid[0].length;
HashMap<String, Integer> hash = new HashMap<>();
for(int i = 0;i<n;++i) {
StringBuilder sb = new StringBuilder();
for(int j = 0;j<m;++j) {
sb.append("#");
sb.append(grid[i][j]);
}
hash.put(sb.toString(), hash.getOrDefault(sb.toString(), 0)+1);
}
for(int j=0;j<n;++j) {
StringBuilder sb = new StringBuilder();
for(int i=0;i<n;++i) {
sb.append("#");
sb.append(grid[i][j]);
}
if(hash.containsKey(sb.toString())) res += hash.getOrDefault(sb.toString(), 0);
}
return res;
}
}
[Leetcode]2353. 设计食物评分系统
设计一个支持下述操作的食物评分系统:
- 修改 系统中列出的某种食物的评分。
- 返回系统中某一类烹饪方式下评分最高的食物。
实现 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] 之前。
方法一:平衡树(有序集合)
我们可以用一个哈希表 \(\textit{fs}\) 记录每个食物名称对应的食物评分和烹饪方式,另一个哈希表套平衡树 \(\textit{cs}\) 记录每个烹饪方式对应的食物评分和食物名字集合。对于 changeRating 操作,先从 \(\textit{cs}[\textit{fs}[\textit{food}].\textit{cuisine}]\) 中删掉旧数据,然后将 \(\textit{newRating}\) 和 \(\textit{food}\) 记录到 \(\textit{cs}\) 和 \(\textit{fs}\) 中。
方法二:懒删除堆
另一种做法是用堆:
- 对于 changeRating 操作,直接往 \(\textit{cs}\) 中记录,不做任何删除操作;
- 对于 highestRated 操作,查看堆顶的食物评分是否等于其实际值,若不相同则意味着对应的元素已被替换成了其他值,堆顶存的是个垃圾数据,直接弹出堆顶;否则堆顶就是答案。
// TreeSet
class FoodRatings1 {
HashMap<String, Pair<String, Integer>> foodDict = new HashMap<>();
HashMap<String, TreeSet<Pair<String, Integer>>> cuisineDict = new HashMap<>();
public FoodRatings(String[] foods, String[] cuisines, int[] ratings) {
for(int i=0;i<foods.length;++i) {
String food = foods[i], cuisine = cuisines[i];
int rating = ratings[i];
foodDict.put(food, new Pair<>(cuisine, rating));
cuisineDict.computeIfAbsent(cuisine, k -> new TreeSet<>((a, b) -> !Objects.equals(a.getValue(), b.getValue()) ? b.getValue()-a.getValue() : a.getKey().compareTo(b.getKey()))).add(new Pair<>(food, rating));
}
}
public void changeRating(String food, int newRating) {
var pair = foodDict.get(food);
String cuisine = pair.getKey();
int oldRating = pair.getValue();
//foodDict.remove(pair);
foodDict.put(food, new Pair<>(cuisine, newRating));
var rawMap = cuisineDict.get(cuisine);
rawMap.remove(new Pair<>(food, pair.getValue()));
rawMap.add(new Pair<>(food, newRating));
}
public String highestRated(String cuisine) {
return cuisineDict.get(cuisine).first().getKey();
}
}
// PriorityQueue
class FoodRatings2 {
HashMap<String, Pair<String, Integer>> foodDict = new HashMap<>();
HashMap<String, PriorityQueue<Pair<String, Integer>>> cuisineDict = new HashMap<>();
public FoodRatings(String[] foods, String[] cuisines, int[] ratings) {
for(int i=0;i<foods.length;++i) {
String food = foods[i], cuisine = cuisines[i];
int rating = ratings[i];
foodDict.put(food, new Pair<>(cuisine, rating));
cuisineDict.computeIfAbsent(cuisine, k -> new PriorityQueue<>((a, b) -> !Objects.equals(a.getValue(), b.getValue()) ? b.getValue()-a.getValue() : a.getKey().compareTo(b.getKey()))).add(new Pair<>(food, rating));
}
}
public void changeRating(String food, int newRating) {
var pair = foodDict.get(food);
String cuisine = pair.getKey();
int oldRating = pair.getValue();
//foodDict.remove(pair);
foodDict.put(food, new Pair<>(cuisine, newRating));
var rawMap = cuisineDict.get(cuisine);
rawMap.offer(new Pair<>(food, newRating));
}
// lazy delete
public String highestRated(String cuisine) {
var rawMap = cuisineDict.get(cuisine);
while(!Objects.equals(rawMap.peek().getValue(), foodDict.get(rawMap.peek().getKey()).getValue())) {
rawMap.poll();
}
return rawMap.peek().getKey();
}
}
[Leetcode]2354. 优质数对的数目
给你一个下标从 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) 也可以是优质数对。
数学。
讨论二进制第 i 位在 num1 和 num2 中是否为 1 的情况:
若第 i 位的 1 只在 num1 和 num2 中出现一次,则它只会在 num1 OR num2 的结果中出现,对位数之和的贡献是 1;
若第 i 位的 1 在 num1 和 num2 中出现两次,则它会在 num1 OR num2 和 num1 AND num2 的结果中出现,对位数之和的贡献是 2。
也就是说,第 i 位在两个数里出现几次,它的贡献就是几。因此我们维护 f(x) 表示数 x 中有几个 1,题目变为:
求不同数对 (x,y) 的数量,使得 \(f(x) + f(y) \ge k\)。
统计答案时,我们枚举 \(f(x)\) 和 \(f(y)\)。记 \(g(t)\) 表示 \(f(x) = t\) 的不同 x 有几个。根据数学知识,满足 \(f(x) + f(y) \ge k\) 的数对有 \(g(f(x)) \times g(f(y))\) 对。把枚举过程中的所有答案加起来即可。复杂度 \(\mathcal{O}(n\log A + \log^2 A)\),其中 A 是数组中的最大元素。
class Solution {
int[] bitcnt = new int[32];
HashSet<Integer> set = new HashSet<>();
public long countExcellentPairs(int[] nums, int k) {
for(var num:nums) {
int n = Integer.bitCount(num);
if(!set.contains(num)) {
bitcnt[n] += 1;
set.add(num);
}
}
long res = 0;
for(int i=0;i<32;++i) {
for(int j=0;j<32;++j) {
if(i+j >= k && bitcnt[i]!=0 &&bitcnt[j]!=0)
res += bitcnt[i] * bitcnt[j];
}
}
return res;
}
}