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])

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值