题目

不使用任何内建的哈希表库设计一个哈希映射

具体地说,你的设计应该包含以下的功能

  • put(key, value):向哈希映射中插入(键,值)的数值对。如果键对应的值已经存在,更新这个值。
  • get(key):返回给定的键所对应的值,如果映射中不包含这个键,返回-1。
  • remove(key):如果映射中存在这个键,删除这个数值对。

示例:

  1. MyHashMap hashMap = new MyHashMap();
  2. hashMap.put(1, 1);
  3. hashMap.put(2, 2);
  4. hashMap.get(1); // 返回 1
  5. hashMap.get(3); // 返回 -1 (未找到)
  6. hashMap.put(2, 1); // 更新已有的值
  7. hashMap.get(2); // 返回 1
  8. hashMap.remove(2); // 删除键为2的数据
  9. hashMap.get(2); // 返回 -1 (未找到)

注意:

  • 所有的值都在 [1, 1000000] 的范围内。
  • 操作的总数目在 [1, 10000] 范围内。
  • 不要使用内建的哈希库。

方案一(切片)

type MyHashMap struct {
    set []bool
    container []int
}

/** Initialize your data structure here. */
func Constructor() MyHashMap {
    return MyHashMap {
        set: make([]bool, 1000001, 1000001),
        container: make([]int, 1000001, 1000001),
    }
}

/** value will always be non-negative. */
func (this *MyHashMap) Put(key int, value int)  {
    this.set[key] = true
    this.container[key] = value
}

/** Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key */
func (this *MyHashMap) Get(key int) int {
    in := this.set[key]
    value := this.container[key]
    if in {
        return value
    } else {
        return -1
    }
}

/** Removes the mapping of the specified value key if this map contains a mapping for the key */
func (this *MyHashMap) Remove(key int)  {
    this.set[key] = false
}

因为值可能是0,所以直接使用一个数组来存储 Get 时会有麻烦。

方案二

参考 leetcode 方案,可使用以下方案

type hash struct {
    value int
}

type MyHashMap struct {
    container []*hash
}

/** Initialize your data structure here. */
func Constructor() MyHashMap {
    return MyHashMap {
        container: make([]*hash, 1000001, 1000001),
    }
}

/** value will always be non-negative. */
func (this *MyHashMap) Put(key int, value int)  {
    hash := &hash {
        value: value,
    }
    this.container[key] = hash
}

/** Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key */
func (this *MyHashMap) Get(key int) int {
    hash := this.container[key]
    if hash != nil {
        return hash.value
    } else {
        return -1
    }
}

/** Removes the mapping of the specified value key if this map contains a mapping for the key */
func (this *MyHashMap) Remove(key int)  {
    this.container[key] = nil
}

方案三

const NUMBER_OF_BUCKET = 1000

type hash struct {
    key int
    value int
}

type MyHashMap struct {
    bucket [][]*hash
}

/** Initialize your data structure here. */
func Constructor() MyHashMap {
    return MyHashMap {
        bucket: make([][]*hash, NUMBER_OF_BUCKET, NUMBER_OF_BUCKET),
    }
}

func (this *MyHashMap)getBucket(key int) []*hash {
    index := key % NUMBER_OF_BUCKET
    return this.bucket[index]
}

/** value will always be non-negative. */
func (this *MyHashMap) Put(key int, value int)  {
    bucket := this.getBucket(key)
    for _, hash := range bucket {
        if hash != nil && hash.key == key {
            hash.value = value
        }
    }

    _hash := &hash{
        key: key,
        value: value,
    }

    index := key % NUMBER_OF_BUCKET
    this.bucket[index] = append(this.bucket[index], _hash)
}

/** Returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key */
func (this *MyHashMap) Get(key int) int {
    bucket := this.getBucket(key)
    for _, hash := range bucket {
        if hash != nil && hash.key == key {
            return hash.value
        }
    }

    return -1
}

/** Removes the mapping of the specified value key if this map contains a mapping for the key */
func (this *MyHashMap) Remove(key int)  {
    bucket := this.getBucket(key)
    for i, hash := range bucket {
        if hash != nil && hash.key == key {
            bucket[i] = nil
        }
    }
}
  • 该方案是性能最好的(来自 leetcode 测试);
  • 其实用链表更好,因为此处没有按索引查询值的需求,而且删除操作此处直接置为nil,使用链表删除此节点会更好些。

更多

我们来看看 删除 操作。在找到元素的位置之后,需要从数组列表中删除元素。

假设我们要删除第 i 个元素,并且数组列表的大小为 n

内置函数中使用的策略是把第 i 个元素后的所有元素向前移动一个位置。也就是说,必须移动 n - i 次。因此,从数组列表中删除元素的时间复杂度将为 O(n)。

考虑 i 取不同值的情况。平均而言,我们将移动 ((n - 1) + (n - 2) + ... + 1 + 0) / n = (n - 1) / 2 次。

有两种解决方案可以将时间复杂度从 设计哈希映射 - 图1 降低到 设计哈希映射 - 图2

  1. 交换

首先,用存储桶中的最后一个元素交换要移除的元素。然后删除最后一个元素。通过这种方法,成功地在 设计哈希映射 - 图3 的时间复杂度中去除了元素。

  1. 链表

实现此目标的另一种方法是使用链表而不是数组列表。通过这种方式,可以在不修改列表中的顺序的情况下删除元素。该策略时间复杂度为 设计哈希映射 - 图4

原文

https://leetcode-cn.com/explore/learn/card/hash-table/203/design-a-hash-table/800/