Design HashSet
Open-addressing or separate-chaining hash set — bucket by hash, linear list inside each bucket.
3 min read
design hash-set
Problem#
Implement MyHashSet with add, remove, contains — without using the built-in hash set.
Examples#
add(1); add(2); contains(1)→true;contains(3)→false.add(2); remove(2); contains(2)→false.
Constraints#
0 <= key <= 10^6, up to10^4calls.
Approach#
Separate chaining. Fixed-size bucket array; each bucket is a list<int>. Hash by key % buckets. Linear scan within a bucket for membership.
Solution#
class MyHashSet { static const int B = 769; // prime vector<list<int>> buckets; list<int>::iterator findIt(int key, int idx) { return find(buckets[idx].begin(), buckets[idx].end(), key); }public: MyHashSet() : buckets(B) {} void add(int key) { int idx = key % B; if (findIt(key, idx) == buckets[idx].end()) buckets[idx].push_back(key); } void remove(int key) { int idx = key % B; auto it = findIt(key, idx); if (it != buckets[idx].end()) buckets[idx].erase(it); } bool contains(int key) { int idx = key % B; return findIt(key, idx) != buckets[idx].end(); }};const buckets = 769 // prime
type MyHashSet struct { table [][]int}
func Constructor() MyHashSet { return MyHashSet{table: make([][]int, buckets)}}
func (s *MyHashSet) findIdx(key, idx int) int { for i, v := range s.table[idx] { if v == key { return i } } return -1}
func (s *MyHashSet) Add(key int) { idx := key % buckets if s.findIdx(key, idx) == -1 { s.table[idx] = append(s.table[idx], key) }}
func (s *MyHashSet) Remove(key int) { idx := key % buckets i := s.findIdx(key, idx) if i != -1 { s.table[idx] = append(s.table[idx][:i], s.table[idx][i+1:]...) }}
func (s *MyHashSet) Contains(key int) bool { idx := key % buckets return s.findIdx(key, idx) != -1}from typing import List
class MyHashSet: _B = 769 # prime
def __init__(self): self.table: List[List[int]] = [[] for _ in range(self._B)]
def add(self, key: int) -> None: bkt = self.table[key % self._B] if key not in bkt: bkt.append(key)
def remove(self, key: int) -> None: bkt = self.table[key % self._B] if key in bkt: bkt.remove(key)
def contains(self, key: int) -> bool: return key in self.table[key % self._B]var MyHashSet = function() { this.B = 769; // prime this.table = Array.from({ length: this.B }, () => []);};
MyHashSet.prototype.add = function(key) { const bkt = this.table[key % this.B]; if (!bkt.includes(key)) bkt.push(key);};
MyHashSet.prototype.remove = function(key) { const bkt = this.table[key % this.B]; const idx = bkt.indexOf(key); if (idx !== -1) bkt.splice(idx, 1);};
MyHashSet.prototype.contains = function(key) { return this.table[key % this.B].includes(key);};class MyHashSet { private static final int B = 769; // prime private LinkedList<Integer>[] table;
public MyHashSet() { table = new LinkedList[B]; for (int i = 0; i < B; i++) table[i] = new LinkedList<>(); }
public void add(int key) { LinkedList<Integer> bkt = table[key % B]; if (!bkt.contains(key)) bkt.add(key); }
public void remove(int key) { LinkedList<Integer> bkt = table[key % B]; bkt.remove(Integer.valueOf(key)); }
public boolean contains(int key) { return table[key % B].contains(key); }}class MyHashSet { private B = 769; // prime private table: number[][];
constructor() { this.table = Array.from({ length: this.B }, () => [] as number[]); }
add(key: number): void { const bkt = this.table[key % this.B]; if (!bkt.includes(key)) bkt.push(key); }
remove(key: number): void { const bkt = this.table[key % this.B]; const idx = bkt.indexOf(key); if (idx !== -1) bkt.splice(idx, 1); }
contains(key: number): boolean { return this.table[key % this.B].includes(key); }}Editorial#
A prime bucket count smooths the distribution if keys cluster on multiples of small numbers. Each bucket stays short (average n / B), so all ops are O(1) average.
Complexity#
- Time:
O(1)average. - Space:
O(n).
Concept revision#
Related#