Want Help Cracking FAANG?

(Then click this)

×
Back to Question Bank

706. Design HashMap - Leetcode Solution

Problem Description

The Leetcode "Design HashMap" problem asks you to implement a data structure that mimics the behavior of a standard hash map (also known as a dictionary or associative array). You must create a class MyHashMap with the following methods:

  • put(key, value): Insert a (key, value) pair into the HashMap. If the key already exists, update its value.
  • get(key): Return the value mapped to key, or -1 if the key does not exist.
  • remove(key): Remove the mapping for key if it exists.

You are not allowed to use any built-in hash table libraries. You must design the underlying data structure and collision handling yourself.

Constraints:

  • All keys and values are non-negative integers.
  • All methods must run in average O(1) time.
  • There will be at most 104 calls in total.

Thought Process

The core challenge is to efficiently store and retrieve key-value pairs without using built-in hash maps. We need to think about how to map a potentially large range of keys (e.g., up to 106 or more) into a fixed-size array, and how to handle cases where two keys map to the same array index (collisions).

A brute-force approach might be to use a large array where each index directly corresponds to a key. But this is memory-inefficient if the key space is sparse. Instead, we use a hash function to map keys to array indices and handle collisions using "chaining" (linked lists or arrays at each index) or "open addressing" (probing for empty slots).

We want our operations (put, get, remove) to be fast, so we choose a hash function and collision handling method that keeps the average number of items per bucket low.

Solution Approach

To design our own hash map, we'll use the following steps:

  1. Create a fixed-size array of buckets:
    • Each bucket will store a list (or linked list) of (key, value) pairs.
    • The number of buckets (array size) is chosen to balance memory usage and collision probability. A typical size is a prime number like 1000 or 10007.
  2. Hash function:
    • We map each key to an index using index = key % num_buckets.
    • This distributes keys uniformly if keys are random.
  3. Chaining for collision handling:
    • If multiple keys hash to the same index, we store all their (key, value) pairs in the same bucket (as a list).
    • When inserting, we check if the key already exists in the bucket and update it if so; otherwise, we append a new pair.
    • For get and remove, we search the bucket for the key.
  4. Implement methods:
    • put: Hash the key, search the bucket, update or insert.
    • get: Hash the key, search the bucket, return value or -1.
    • remove: Hash the key, search the bucket, remove if found.

This structure ensures that all operations have average-case O(1) time, assuming a good hash function and a reasonable load factor (number of keys per bucket).

Example Walkthrough

Let's walk through an example with the following operations:

  MyHashMap()
  put(1, 10)
  put(2, 20)
  get(1)      # returns 10
  get(3)      # returns -1 (not found)
  put(2, 30)  # update value for key=2
  get(2)      # returns 30
  remove(2)
  get(2)      # returns -1 (removed)
  
  1. put(1, 10): Hash 1 to an index (e.g., 1 % 1000 = 1). Insert (1, 10) in bucket 1.
  2. put(2, 20): Hash 2 to index 2. Insert (2, 20) in bucket 2.
  3. get(1): Hash 1 to index 1. Find (1, 10) in bucket 1, return 10.
  4. get(3): Hash 3 to index 3. Bucket 3 is empty, return -1.
  5. put(2, 30): Hash 2 to index 2. Find (2, 20) in bucket 2, update to (2, 30).
  6. get(2): Hash 2 to index 2. Find (2, 30) in bucket 2, return 30.
  7. remove(2): Hash 2 to index 2. Remove (2, 30) from bucket 2.
  8. get(2): Hash 2 to index 2. Bucket 2 is now empty, return -1.

At every step, we only need to look in one bucket, so all operations are fast.

Time and Space Complexity

  • Brute-force approach:
    • If we used a giant array of size MAX_KEY (e.g., 106), put, get, and remove would be O(1) time, but space would be O(MAX_KEY), which is wasteful if keys are sparse.
  • Optimized hash map approach:
    • Time Complexity:
      • Average-case for put, get, remove is O(1), because each bucket contains few items if the hash function is good and the number of buckets is large enough.
      • Worst-case is O(N) if all keys hash to the same bucket (very unlikely with a good hash function).
    • Space Complexity:
      • O(N + B), where N is the number of stored key-value pairs and B is the number of buckets.

Summary

In this problem, we built a simple hash map from scratch using an array of buckets and a hash function, handling collisions by chaining. The key insight is that by distributing keys across buckets and resolving collisions locally, we achieve fast average-case operations without excessive memory usage. This approach mirrors how real-world hash tables work and is a fundamental data structure in computer science.

Code Implementation

class MyHashMap:
    def __init__(self):
        self.size = 1009  # use a prime number for better distribution
        self.buckets = [[] for _ in range(self.size)]

    def put(self, key: int, value: int) -> None:
        idx = key % self.size
        for i, (k, v) in enumerate(self.buckets[idx]):
            if k == key:
                self.buckets[idx][i] = (key, value)
                return
        self.buckets[idx].append((key, value))

    def get(self, key: int) -> int:
        idx = key % self.size
        for k, v in self.buckets[idx]:
            if k == key:
                return v
        return -1

    def remove(self, key: int) -> None:
        idx = key % self.size
        bucket = self.buckets[idx]
        for i, (k, v) in enumerate(bucket):
            if k == key:
                del bucket[i]
                return
      
class MyHashMap {
    static const int SIZE = 1009; // Use a prime number for hash table size
    vector<list<pair<int,int>>> buckets;
public:
    MyHashMap() {
        buckets.resize(SIZE);
    }

    void put(int key, int value) {
        int idx = key % SIZE;
        for (auto& kv : buckets[idx]) {
            if (kv.first == key) {
                kv.second = value;
                return;
            }
        }
        buckets[idx].emplace_back(key, value);
    }

    int get(int key) {
        int idx = key % SIZE;
        for (auto& kv : buckets[idx]) {
            if (kv.first == key) return kv.second;
        }
        return -1;
    }

    void remove(int key) {
        int idx = key % SIZE;
        for (auto it = buckets[idx].begin(); it != buckets[idx].end(); ++it) {
            if (it->first == key) {
                buckets[idx].erase(it);
                return;
            }
        }
    }
};
      
class MyHashMap {
    private static final int SIZE = 1009; // Prime number for buckets
    private LinkedList<int[]>[] buckets;

    public MyHashMap() {
        buckets = new LinkedList[SIZE];
        for (int i = 0; i < SIZE; i++) {
            buckets[i] = new LinkedList<>();
        }
    }

    public void put(int key, int value) {
        int idx = key % SIZE;
        for (int[] pair : buckets[idx]) {
            if (pair[0] == key) {
                pair[1] = value;
                return;
            }
        }
        buckets[idx].add(new int[]{key, value});
    }

    public int get(int key) {
        int idx = key % SIZE;
        for (int[] pair : buckets[idx]) {
            if (pair[0] == key) return pair[1];
        }
        return -1;
    }

    public void remove(int key) {
        int idx = key % SIZE;
        Iterator<int[]> it = buckets[idx].iterator();
        while (it.hasNext()) {
            if (it.next()[0] == key) {
                it.remove();
                return;
            }
        }
    }
}
      
var MyHashMap = function() {
    this.size = 1009; // prime number for bucket size
    this.buckets = Array.from({length: this.size}, () => []);
};

MyHashMap.prototype.put = function(key, value) {
    const idx = key % this.size;
    for (let i = 0; i < this.buckets[idx].length; i++) {
        if (this.buckets[idx][i][0] === key) {
            this.buckets[idx][i][1] = value;
            return;
        }
    }
    this.buckets[idx].push([key, value]);
};

MyHashMap.prototype.get = function(key) {
    const idx = key % this.size;
    for (const [k, v] of this.buckets[idx]) {
        if (k === key) return v;
    }
    return -1;
};

MyHashMap.prototype.remove = function(key) {
    const idx = key % this.size;
    for (let i = 0; i < this.buckets[idx].length; i++) {
        if (this.buckets[idx][i][0] === key) {
            this.buckets[idx].splice(i, 1);
            return;
        }
    }
};