706. Design HashMap - Explanation

Problem Link

Description

Design a HashMap without using any built-in hash table libraries.

Implement the MyHashMap class:

  • MyHashMap() initializes the object with an empty map.
  • void put(int key, int value) inserts a (key, value) pair into the HashMap. If the key already exists in the map, update the corresponding value.
  • int get(int key) returns the value to which the specified key is mapped, or -1 if this map contains no mapping for the key.
  • void remove(key) removes the key and its corresponding value if the map contains the mapping for the key.

Example 1:

Input: ["MyHashMap", "put", "put", "get", "get", "put", "get", "remove", "get"]
[[], [1, 1], [2, 2], [1], [3], [2, 1], [2], [2], [2]]

Output: [null, null, null, 1, -1, null, 1, null, -1]

Explanation:
MyHashMap myHashMap = new MyHashMap();
myHashMap.put(1, 1); // The map is now [[1,1]]
myHashMap.put(2, 2); // The map is now [[1,1], [2,2]]
myHashMap.get(1); // return 1, The map is now [[1,1], [2,2]]
myHashMap.get(3); // return -1 (i.e., not found), The map is now [[1,1], [2,2]]
myHashMap.put(2, 1); // The map is now [[1,1], [2,1]] (i.e., update the existing value)
myHashMap.get(2); // return 1, The map is now [[1,1], [2,1]]
myHashMap.remove(2); // remove the mapping for 2, The map is now [[1,1]]
myHashMap.get(2); // return -1 (i.e., not found), The map is now [[1,1]]

Constraints:

  • 0 <= key, value <= 1,000,000
  • At most 10,000 calls will be made to put, get, and remove.


Company Tags

Please upgrade to NeetCode Pro to view company tags.



1. Array

Intuition

Since keys are constrained to the range [0, 1000000], we can use direct addressing. We allocate an array where the index represents the key and the value at that index is the stored value. We use -1 to indicate that a key is not present. This gives O(1) time for all operations at the cost of fixed memory usage regardless of how many keys are actually stored.

Algorithm

  1. Initialize an array of size 1000001 with all values set to -1.
  2. For put(key, value): Set map[key] = value.
  3. For get(key): Return map[key] (returns -1 if the key was never set or was removed).
  4. For remove(key): Set map[key] = -1.
class MyHashMap:

    def __init__(self):
        self.map = [-1] * 1000001

    def put(self, key: int, value: int) -> None:
        self.map[key] = value

    def get(self, key: int) -> int:
        return self.map[key]

    def remove(self, key: int) -> None:
        self.map[key] = -1

Time & Space Complexity

  • Time complexity: O(1)O(1) for each function call.
  • Space complexity: O(1000000)O(1000000) since the key is in the range [0,1000000][0, 1000000].

2. Linked List

Intuition

To reduce memory usage, we use a hash table with separate chaining. We create an array of buckets (smaller than the key range) and use a hash function (key modulo bucket count) to determine which bucket a key belongs to. Each bucket is a linked list that stores key-value pairs. This handles collisions by chaining multiple entries in the same bucket.

Algorithm

  1. Initialize an array of 1000 buckets, each containing a dummy head node for a linked list.
  2. Define hash(key) as key % 1000.
  3. For put(key, value): Traverse the linked list at hash(key). If a node with the matching key exists, update its value. Otherwise, append a new node with the key-value pair.
  4. For get(key): Traverse the linked list at hash(key). If a node with the matching key is found, return its value. Otherwise, return -1.
  5. For remove(key): Traverse the linked list at hash(key). If a node with the matching key is found, remove it by updating the previous node's next pointer.
class ListNode:
    def __init__(self, key = -1, val = -1, next = None):
        self.key = key
        self.val = val
        self.next = next

class MyHashMap:

    def __init__(self):
        self.map = [ListNode() for _ in range(1000)]

    def hash(self, key: int) -> int:
        return key % len(self.map)

    def put(self, key: int, value: int) -> None:
        cur = self.map[self.hash(key)]
        while cur.next:
            if cur.next.key == key:
                cur.next.val = value
                return
            cur = cur.next
        cur.next = ListNode(key, value)

    def get(self, key: int) -> int:
        cur = self.map[self.hash(key)].next
        while cur:
            if cur.key == key:
                return cur.val
            cur = cur.next
        return -1

    def remove(self, key: int) -> None:
        cur = self.map[self.hash(key)]
        while cur.next:
            if cur.next.key == key:
                cur.next = cur.next.next
                return
            cur = cur.next

Time & Space Complexity

  • Time complexity: O(nk)O(\frac{n}{k}) for each function call.
  • Space complexity: O(k+m)O(k + m)

Where nn is the number of keys, kk is the size of the map (10001000) and mm is the number of unique keys.