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.


Topics

Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Hash Functions - Understanding how keys map to indices via modulo operations
  • Linked Lists - Implementing node-based structures for collision handling (chaining)
  • Arrays - Using direct addressing with fixed-size arrays

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.


Common Pitfalls

Not Updating Existing Keys on Put

When a key already exists, put() should update its value rather than adding a duplicate entry.

# Wrong - creates duplicate entries
def put(self, key: int, value: int) -> None:
    cur = self.map[self.hash(key)]
    while cur.next:
        cur = cur.next
    cur.next = ListNode(key, value)  # Always adds new node!

# Correct - update if exists, otherwise add
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  # Update existing
            return
        cur = cur.next
    cur.next = ListNode(key, value)  # Add new

Traversing from Dummy Node in Get

When getting a value, start from dummy.next (actual first element), not the dummy head itself.

# Wrong - checks dummy node's key
def get(self, key: int) -> int:
    cur = self.map[self.hash(key)]  # Starts at dummy
    while cur:
        if cur.key == key:  # Dummy has key=-1, might match!
            return cur.val
        cur = cur.next
    return -1

# Correct - skip dummy node
def get(self, key: int) -> int:
    cur = self.map[self.hash(key)].next  # Start after dummy
    while cur:
        if cur.key == key:
            return cur.val
        cur = cur.next
    return -1