706. Design HashMap - Explanation
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 thekeyalready exists in the map, update the correspondingvalue.int get(int key)returns thevalueto which the specifiedkeyis mapped, or-1if this map contains no mapping for thekey.void remove(key)removes thekeyand its correspondingvalueif the map contains the mapping for thekey.
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,000calls will be made toput,get, andremove.
Topics
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
- Initialize an array of size
1000001with all values set to-1. - For
put(key, value): Setmap[key] = value. - For
get(key): Returnmap[key](returns-1if the key was never set or was removed). - For
remove(key): Setmap[key] = -1.
Time & Space Complexity
- Time complexity: for each function call.
- Space complexity: since the key is in the range .
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
- Initialize an array of
1000buckets, each containing a dummy head node for a linked list. - Define
hash(key)askey % 1000. - For
put(key, value): Traverse the linked list athash(key). If a node with the matching key exists, update its value. Otherwise, append a new node with the key-value pair. - For
get(key): Traverse the linked list athash(key). If a node with the matching key is found, return its value. Otherwise, return-1. - For
remove(key): Traverse the linked list athash(key). If a node with the matching key is found, remove it by updating the previous node'snextpointer.
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.nextTime & Space Complexity
- Time complexity: for each function call.
- Space complexity:
Where is the number of keys, is the size of the map () and 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 newTraversing 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
Sign in to join the discussion