705. 设计哈希集合

331 阅读1分钟

题目介绍

力扣705题:leetcode-cn.com/problems/de…

image.png

方法一:简单数组

由于题目给出了 0 <= key <= 10^6 数据范围,同时限定了 key 只能是 int。

我们可以直接使用一个 boolean 数组记录某个 key 是否存在,key 直接对应 boolean 的下标。

代码如下:

class MyHashSet {
    boolean[] nodes = new boolean[1000009];
    
    /**
     * 添加元素
     */
    public void add(int key) {
        nodes[key] = true;
    }
    
    /**
     * 移除元素
     */
    public void remove(int key) {
        nodes[key] = false;
    }
    
    /**
     * 判断元素是否在数组中
     */
    public boolean contains(int key) {
        return nodes[key];
    }
}

方法二:链地址法

为了实现哈希集合这一数据结构,有以下几个关键问题需要解决:

  • 哈希函数:能够将集合中任意可能的元素映射到一个固定范围的整数值,并将该元素存储到整数值对应的地址上。

  • 冲突处理:由于不同元素可能映射到相同的整数值,因此需要在整数值出现「冲突」时,需要进行冲突处理。总的来说,有以下几种策略解决冲突:

    • 链地址法:为每个哈希值维护一个链表,并将具有相同哈希值的元素都放入这一链表当中。
    • 开放地址法:当发现哈希值 h 处产生冲突时,根据某种策略,从 h 出发找到下一个不冲突的位置。例如,一种最简单的策略是,不断地检查 h + 1, h + 2, h + 3,… 这些整数对应的位置。
    • 再哈希法:当发现哈希冲突后,使用另一个哈希函数产生一个新的地址。
  • 扩容:当哈希表元素过多时,冲突的概率将越来越大,而在哈希表中查询一个元素的效率也会越来越低。因此,需要开辟一块更大的空间,来缓解哈希表中发生的冲突。

设哈希表的大小为 base,则可以设计一个简单的哈希函数:hash(x)=x mod base。

我们开辟一个大小为 base 的数组,数组的每个位置是一个链表。当计算出哈希值之后,就插入到对应位置的链表当中。

由于我们使用整数除法作为哈希函数,为了尽可能避免冲突,应当将 base 取为一个质数。在这里,我们取 base=769。

image.png

代码如下:

class MyHashSet {
    private static final int BASE = 769;
    private LinkedList[] data;

    /** Initialize your data structure here. */
    public MyHashSet() {
        data = new LinkedList[BASE];
        for (int i = 0; i < BASE; ++i) {
            data[i] = new LinkedList<Integer>();
        }
    }
    
    public void add(int key) {
        int h = hash(key);
        Iterator<Integer> iterator = data[h].iterator();
        while (iterator.hasNext()) {
            Integer element = iterator.next();
            //判断是否已经存在key,如果已经存在则直接返回,什么也不做
            if (element == key) {
                return;
            }
        }
        //将key添加到链表的末尾
        data[h].offerLast(key);
    }
    
    public void remove(int key) {
        int h = hash(key);
        Iterator<Integer> iterator = data[h].iterator();
        while (iterator.hasNext()) {
            Integer element = iterator.next();
            if (element == key) {
                data[h].remove(element);
                return;
            }
        }
    }
    
    /** Returns true if this set contains the specified element */
    public boolean contains(int key) {
        int h = hash(key);
        Iterator<Integer> iterator = data[h].iterator();
        while (iterator.hasNext()) {
            Integer element = iterator.next();
            if (element == key) {
                return true;
            }
        }
        return false;
    }

    private static int hash(int key) {
        //取模的方式
        return key % BASE;
    }
}

复杂度分析

  • 时间复杂度:O(n / b)。其中 n 为哈希表中的元素数量,b 为链表的数量。假设哈希值是均匀分布的,则每个链表大概长度为 n / b。

  • 空间复杂度:O(n+b)。