在问 · 算法题解

705. 设计哈希集合

简单设计链地址法力扣原题 ↗

题目描述

不借助任何语言自带的哈希表库,自己实现一个整数集合类 MyHashSet。

MyHashSet():创建空集合。add(key):把 key 加入集合,已存在时不变,无返回值。remove(key):把 key 从集合中删掉,不存在时什么也不做,无返回值。contains(key):集合中有 key 时返回 true,否则返回 false。

测试数据以方法名序列和参数序列给出,输出数组是每次调用的返回值,无返回值的调用对应 null。

示例 1
输入:操作 = ["MyHashSet","add","add","add","remove","contains","add","add","add","remove","contains","add","add","add","remove","contains","add","add","add","remove","contains"]
参数 = [[],[1],[10001],[1],[1],[1],[7],[10007],[7],[7],[7],[123],[10123],[123],[123],[123],[5000],[15000],[5000],[5000],[5000]]
输出:[null,null,null,null,null,false,null,null,null,null,false,null,null,null,null,false,null,null,null,null,false]

解释:每组操作都先加入某个数(含重复加入),再把它删掉,所以随后的 contains 都返回 false。

数据范围

  • 0 <= key <= 10^6
  • add、remove、contains 合计最多调用 10^4 次。

解题思路:链地址法

链地址法:开 base 个桶(取质数 769),key 放进第 key % base 个桶。每个桶是一个小链表/数组,冲突的 key 挤在同一个桶里。add 前先查桶里是否已有,避免重复;remove 只在桶里找到时删除;contains 只查一个桶。

为什么正确

任意时刻,每个已加入的 key 恰好出现一次,且只出现在第 key % base 个桶中。

复杂度分析

设 n 为元素个数,每次操作平均 O(1 + n / base);空间 O(base + n)。

易错点

add 前一定要先判断是否已存在,否则重复 add 后 remove 一次仍然能查到。remove 一个不存在的 key 不能报错。桶数取质数可以让 key 分布更均匀。

参考代码

Python、Java、C++、Go、JavaScript、TypeScript 六种语言,逻辑一致。

class MyHashSet:
    def __init__(self, base: int = 769):
        self.base = base
        self.buckets = [[] for _ in range(base)]

    def add(self, key: int) -> None:
        b = self.buckets[key % self.base]
        if key not in b:
            b.append(key)

    def remove(self, key: int) -> None:
        b = self.buckets[key % self.base]
        if key in b:
            b.remove(key)

    def contains(self, key: int) -> bool:
        b = self.buckets[key % self.base]
        return key in b

载入编辑器,自己跑一遍 →

其他解法

链地址法用取模把 key 分到少量桶里,内存随元素数增长;位图直接按值寻址,快但内存随值域增长。

解法 2:位图 时间 O(1),空间 O(U / 32)

key 的范围是 0 到 10⁶,可以给每个可能的 key 分配一个二进制位。用 32 位整数数组存放,key 对应第 key / 32 个整数的第 key % 32 位。add 把该位置 1,remove 把该位清 0,contains 检查该位。

每次操作都是 O(1),没有冲突。代价是内存取决于 key 的值域 U 而不是元素个数,值域很大时不可用。

class MyHashSet:
    def __init__(self):
        self.bits = [0] * (1000001 // 32 + 1)

    def add(self, key: int) -> None:
        self.bits[key >> 5] |= 1 << (key & 31)

    def remove(self, key: int) -> None:
        self.bits[key >> 5] &= ~(1 << (key & 31))

    def contains(self, key: int) -> bool:
        return self.bits[key >> 5] >> (key & 31) & 1 == 1

设计 专题的其他题目

查看 设计 全部题目 →