Leetcode 705 设计哈希集合☆ (哈希集合)
【不定长拉链数组法】速度最快
大块的内存分配也需要时间。因此避免大块的内存分配,节省空间的同时也是在节省时间。
hashmap哈希表
哈希表可以用来快速判断一个元素是否在集合中,我们可以认为数组就是哈希表
如果我们想要访问数组里的一个元素,只需要O(1)时间复杂度
对于哈希结合hashset的解释
哈希集合(hashset)是指可以在O(1)时间复杂度进行数据的插入,删除,读取
可以保存不重复元素的一种数据结构
===============================================================================
- 题目
解法1 使用超大数组
能使用超大数组来解决本题是因为输入数据的范围在 0<=key<=10
因此我们只需要 10^6 +1
大小的数组,就能让每个数据都有一个单独的索引,不会有 key 的碰撞问题。
因为对于 HashSet 来说,我们只需要关注一个 key 是否存在,而不是 key:value 形式的 HashMap,因此,我们把数组的元素设计成 bool 型的,当某个 key 的对应的数组中的位置取值为 true 说明该 key 存在,取值为 false,说明该 key 不存在。
优点:查找和删除的性能非常快,只用访问 1 次数组;
缺点:使用了非常大的空间,当元素范围很大时,无法使用该方法;当存储的元素个数较少时,性价比极低;需要预知数据的取值范围。
时间复杂度 O(1)
空间复杂度 O(数据范围)
class MyHashSet:
def __init__(self):
self.MyHashSet = 1000001*[False]
def add(self, key: int) -> None:
self.MyHashSet[key] = True
def remove(self, key: int) -> None:
self.MyHashSet[key] = False
def contains(self, key: int) -> bool:
return self.MyHashSet[key]
# Your MyHashSet object will be instantiated and called as such:
# obj = MyHashSet()
# obj.add(key)
# obj.remove(key)
# param_3 = obj.contains(key)
解法2 拉链法
这种方法相对来说节约空间,但是性能上肯定不如超大数组
在存储数据的过程中,如果发生冲突,可以利用链表在已有数据的后面插入新数据 来解决冲突。这种方法被称为“链地址法”
1.定长拉链数组 + 优化手段:如果需要哈希值所在的桶 这时候才会初始化这个桶里面的内容
时间复杂度O(1) 相当于是二维数组的访问 时间复杂度为1
空间复杂度O(数据范围)
这个方法本质上就是把 HashSet 设计成一个 M∗N 的二维数组。
第一个维度用于计算 hash 分桶,每个桶里面可以以链表或者数组的形式放具体的key,第二个维度寻找 key 存放具体的位置。
用了一个优化:第二个维度的数组只有当需要构建时才会产生,这样可以节省内存。
优点:两个维度都可以直接计算出来,查找和删除只用两次访问内存。
缺点:需要预知数据范围,用于设计第二个维度的数组大小。
下面是提供的MyHashSet类中每行代码的解释:
这是MyHashSet类的构造方法。它初始化了三个实例变量:buckets、itemsPerBucket和table。buckets和itemsPerBucket都是整数,用于确定table列表的大小。table是一个包含buckets个子列表的列表,每个子列表都有itemsPerBucket个元素。表达式[_ for _ in range(self.buckets)]创建一个新列表,其中包含self.buckets个元素,每个元素都是一个空列表。
class MyHashSet:
def __init__(self):
self.buckets = 1000
self.itemsPerBucket = 1001
self.table = [[] for _ in range(self.buckets)]
这是一个辅助方法,它以key作为输入并返回该键的哈希值。哈希值计算为键除以桶数的余数。
def hash(self, key):
return key % self.buckets
这是另一个辅助方法,它以key作为输入并返回该键在其对应桶中的位置。位置计算为键除以桶数的整数除法。
def pos(self, key):
return key // self.buckets
此方法将新的key添加到哈希集中。它首先使用hash()方法计算键的哈希值。如果table列表中对应的桶为空,则使用新的itemsPerBucket个零初始化它。然后,它将桶中键的位置的元素设置为1。
def add(self, key):
hashkey = self.hash(key)
if not self.table[hashkey]:
self.table[hashkey] = [0] * self.itemsPerBucket
self.table[hashkey][self.pos(key)] = 1
此方法从哈希集中删除key。它首先使用hash()方法计算键的哈希值。如果table列表中对应的桶不为空,则将桶中键的位置的元素设置为0。
def remove(self, key):
hashkey = self.hash(key)
if self.table[hashkey]:
self.table[hashkey][self.pos(key)] = 0
此方法检查哈希集中是否存在key。它首先使用hash()方法计算键的哈希值。然后,它检查table列表中对应的桶是否不为空,并且桶中键的位置的元素是否为1。如果两个条件都为真,则返回True。否则,返回False。
def contains(self, key):
hashkey = self.hash(key)
return (self.table[hashkey] != []) and (self.table[hashkey][self.pos(key)] == 1)
最终代码如下:
class MyHashSet:
# 定长拉链数组 题目中key范围是0~10^6
def __init__(self):
self.buckets = 1000 # 1000个桶
self.itemPerBucket = 1001 # 每个桶里面的元素
# 初始化1000个桶
self.table = [[] for _ in range(self.buckets)]
def hash(self,key): # 根据key返回该key的哈希值 哈希值=key/哈系桶数1000 取余
return key % self.buckets
def position(self,key): # 根据key返回该key在哈希桶中的位置 位置=key/哈希桶数的整数除法
return key // self.buckets
def add(self, key: int) -> None: # 添加新的元素
#初始化的时候是有目的的初始化(优化手段:只有计算出哈希值的时候才初始化桶里面的内容为0)
hashkey = self.hash(key)
if not self.table[hashkey]: #如果hash桶不为空,则初始化这个桶
self.table[hashkey] = self.itemPerBucket *[0]
# 添加新的元素
self.table[hashkey][self.position(key)] = 1
def remove(self, key: int) -> None:
hashkey = self.hash(key)
if self.table[hashkey]:
self.table[hashkey][self.position(key)] = 0
def contains(self, key: int) -> bool:
hashkey = self.hash(key)
# 当对应的桶不为空 且里面有这个元素的时候 返回True
return ((self.table[hashkey] != []) and self.table[hashkey][self.position(key)] == 1)
# Your MyHashSet object will be instantiated and called as such:
# obj = MyHashSet()
# obj.add(key)
# obj.remove(key)
# param_3 = obj.contains(key)
2.不定长拉链数组
时间复杂度O(N/b) >>> N 是元素个数,b 是桶数 【遍历桶里的链表需要时间】
空间复杂度O(数据范围)
不定长的拉链数组的设计:拉链会根据分桶中的 key 动态增长,更类似于真正的链表。
👍分桶数一般取质数,这是因为经验上来说,质数个的分桶能让数据更加分散到各个桶中
优点:节省内存,不用预知数据范围
缺点:在链表中查找元素需要遍历
class MyHashSet:
# 不定长拉链数组 拉链会根据分桶中的 key 动态增长
def __init__(self):
self.buckets = 1009 # 1009个桶 分桶数量取质数
# 初始化1009个桶
self.table = [[] for _ in range(self.buckets)]
def hash(self,key): # 根据key返回该key的哈希值 哈希值=key/哈系桶数1009 取余
return key % self.buckets
def add(self, key: int) -> None: # 添加新的元素 原先没有的话就append
hashkey = self.hash(key)
if key not in self.table[hashkey]:
self.table[hashkey].append(key)
def remove(self, key: int) -> None: # 删除元素 原先有的话就pop
hashkey = self.hash(key)
if key in self.table[hashkey]:
self.table[hashkey].remove(key)
def contains(self, key: int) -> bool:
hashkey = self.hash(key)
# 当对应的哈希值的桶里面有元素key的时候 返回True
return (key in self.table[hashkey])