题目如下:## 游戏排名第三大的分数
问题描述
小M想要通过查看往届游戏比赛的排名来确定自己比赛的目标分数。他希望找到往届比赛中排名第三的分数,作为自己的目标。具体规则如下:
- 如果分数中有三个或以上不同的分数,返回其中第三大的分数。
- 如果不同的分数只有两个或更少,那么小M将选择最大的分数作为他的目标。
请你帮小M根据给定的分数数组计算目标分数。
import java.util.Set;
import java.util.Arrays;
import java.util.Collections;
public class Main {
public static int solution(int n, int[] nums) {
Set<Integer> Scores = new HashSet<>();
for (int num : nums) {
Scores.add(num);
}
Integer[] scoresArray = Scores.toArray(new Integer[0]);
Arrays.sort(scoresArray, Collections.reverseOrder());
if (scoresArray.length < 3) {
return scoresArray[0];
} else {
return scoresArray[2];
}
}
public static void main(String[] args) {
System.out.println(solution(3, new int[] { 3, 2, 1 }) == 1);
System.out.println(solution(2, new int[] { 1, 2 }) == 2);
System.out.println(solution(4, new int[] { 2, 2, 3, 1 }) == 1);
}
}
代码和题目很简单,主要是利用HashSet的去重和基于哈希的快速查找,所以这次的主题主要介绍HashSet.
由源码可知,HashSet实际是构造了HashMap。
而HashMap是基于哈希表实现,底层由数组+链表+红黑树实现
其特点包括:
1. 基于哈希表
HashMap 在底层使用哈希表来存储数据。哈希表是一种数据结构,它通过哈希函数将键(key)映射到表中的一个位置,以便快速访问。
2. 哈希函数
当你将键值对放入 HashMap 时,它使用键的 hashCode() 方法来计算哈希码,然后通过哈希函数将哈希码转换为数组索引,这个索引就是键值对在哈希表中的存储位置。
3. 处理哈希冲突
由于不同的键可能产生相同的哈希码,这种情况称为哈希冲突。在 HashMap 中,冲突是通过链地址法解决的,即在每个数组位置上维护一个链表。当发生冲突时,新的元素会被添加到链表的头部。
4. 动态扩容
HashMap 会根据需要动态地调整其大小。当HashMap中的元素数量超过负载因子(load factor)与当前容量的乘积时,HashMap会进行扩容,通常是将容量增加一倍,并重新计算所有元素的哈希值以分配到新的位置上。
5. 负载因子
负载因子是一个衡量HashMap中有多“满”的指标。它定义为HashMap中元素数量与桶(bucket)数量的比值。默认的负载因子是0.75(可以自定义),这意味着当HashMap中的元素数量达到75%的桶数量时,HashMap会进行扩容。
6. 线程不安全
HashMap 是非线程安全的。在多线程环境中,如果没有外部同步,多个线程可能会同时修改HashMap,导致数据不一致。如果需要线程安全,可以使用 ConcurrentHashMap 或在使用 HashMap 时进行外部同步。
7. 性能
HashMap 提供了快速的查找、插入和删除操作,平均时间复杂度为 O(1)。但是,在极端情况下(如哈希冲突非常多时),这些操作的时间复杂度可能退化到 O(n)。
go
而在go中,是没有HashSet的,我们可以用自带的map来实现
import (
"fmt"
)
func main() {
hashSet := make(map[string]struct{})
// 添加元素
hashSet["apple"] = struct{}{}
hashSet["banana"] = struct{}{}
hashSet["cherry"] = struct{}{}
// 删除元素
delete(hashSet, "apple")
// 遍历HashSet
for key := range hashSet {
fmt.Println(key)
}
}