Design HashSet — Solution in Python, Java & C++
Design HashSet solution explained: step-by-step approach, complexity and code in Python, Java, C++ and JavaScript. An easy problem on arrays and hash table.
Design HashSet is an easy coding problem on arrays and hash table, asked in Amazon, Google, Microsoft and Apple interviews.
- Difficulty: Easy
- Topics: Arrays, Hash Table
- Asked at: Amazon, Google, Microsoft, Apple
- Time limit: 2 s
- Memory limit: 256 MB
- Languages: JavaScript, TypeScript, Python, Java, C++, C, C#, Go, Kotlin, Swift, Rust, PHP and Ruby
Problem statement
Build a set of integers without using your language's built-in hash table or set. Write the class MyHashSet:
MyHashSet()creates an empty set.void add(int key)putskeyinto the set (adding a key that is already there changes nothing).void remove(int key)takeskeyout of the set; if it is not there, nothing happens.boolean contains(int key)returns whetherkeyis in the set.
The judge calls the methods in the order listed in the first input line, with the arguments in the second; the expected output holds each call's result, null for the constructor and for every void method.
Example 1
Input: ["MyHashSet","add","add","contains","contains","add","contains","remove","contains"]
[[],[3],[8],[3],[5],[8],[8],[8],[8]]
Output: [null,null,null,true,false,null,true,null,false]
Explanation: 3 and 8 go in; 5 never did. Adding 8 a second time changes nothing, and once 8 is removed it is gone.
Example 2
Input: ["MyHashSet","contains","remove","add","add","contains","contains"]
[[],[0],[0],[1000000],[0],[1000000],[0]]
Output: [null,false,null,null,null,true,true]
Explanation: Removing a key that was never added is allowed and does nothing. 0 and 1000000 are the smallest and largest keys.
Constraints
0 <= key <= 10^6At most 10^4 calls are made to add, remove and contains
Hints for Design HashSet
Hint 1
With keys up to 10^6 a boolean array would work, but the point is the general technique: map each key to one of a fixed number of buckets.
Hint 2
Use
key % Bfor a primeBsuch as 769 as the bucket index. Different keys can land in the same bucket — that is a collision.Hint 3
Make each bucket a small list (separate chaining):
addappends only if the key is not already in that bucket's list,removedeletes it from there,containsscans that one list.
How to solve Design HashSet
Separate chaining: an array of B buckets, each a short list of keys. A key lives in bucket key % B, so every operation only looks at one bucket's list instead of the whole set.
Approach
- Create
B = 769empty buckets (a prime spreads keys evenly). add(key): look in bucketkey % B; appendkeyif it is not already there.remove(key): look in bucketkey % B; deletekeyif it is there.contains(key): report whether bucketkey % Bholdskey.
Why it works
Every key is only ever stored in, searched in and removed from the one bucket its hash picks, so a key is in the set exactly when it is in that bucket's list, and the duplicate check on add keeps each key there at most once.
Complexity
- Time —
O(n / B) per operation on average, where n is the number of keys stored - Space —
O(B + n)
Pitfalls
- Appending without checking first lets a key appear twice in a bucket; one
removewould then leave a copy behind. - In Java,
list.remove(key)with anintremoves by index — removeInteger.valueOf(key).
Reference solution
Python
class MyHashSet:
def __init__(self):
self.size = 769
self.buckets = [[] for _ in range(self.size)]
def add(self, key: int) -> None:
b = self.buckets[key % self.size]
if key not in b:
b.append(key)
def remove(self, key: int) -> None:
b = self.buckets[key % self.size]
if key in b:
b.remove(key)
def contains(self, key: int) -> bool:
return key in self.buckets[key % self.size]Java
class MyHashSet {
private static final int SIZE = 769;
private List<List<Integer>> buckets = new ArrayList<>();
public MyHashSet() {
for (int i = 0; i < SIZE; i++) buckets.add(new LinkedList<>());
}
public void add(int key) {
List<Integer> b = buckets.get(key % SIZE);
if (!b.contains(key)) b.add(key);
}
public void remove(int key) {
buckets.get(key % SIZE).remove(Integer.valueOf(key)); // by value, not by index
}
public boolean contains(int key) {
return buckets.get(key % SIZE).contains(key);
}
}C++
class MyHashSet {
static const int SIZE = 769;
vector<list<int>> buckets;
public:
MyHashSet() : buckets(SIZE) {}
void add(int key) {
list<int>& b = buckets[key % SIZE];
if (find(b.begin(), b.end(), key) == b.end()) b.push_back(key);
}
void remove(int key) {
buckets[key % SIZE].remove(key);
}
bool contains(int key) {
list<int>& b = buckets[key % SIZE];
return find(b.begin(), b.end(), key) != b.end();
}
};JavaScript
var MyHashSet = function() {
this.size = 769;
this.buckets = [];
for (var i = 0; i < this.size; i++) this.buckets.push([]);
};
MyHashSet.prototype.add = function(key) {
var b = this.buckets[key % this.size];
if (b.indexOf(key) < 0) b.push(key);
};
MyHashSet.prototype.remove = function(key) {
var b = this.buckets[key % this.size];
var i = b.indexOf(key);
if (i >= 0) b.splice(i, 1);
};
MyHashSet.prototype.contains = function(key) {
return this.buckets[key % this.size].indexOf(key) >= 0;
};Also on the editorial tab: C, C#, Go, Kotlin, PHP, Ruby, Rust, Swift, TypeScript.
All 1199 arrays problems · the whole catalogue
Learn the technique: Arrays · Hashing: Hash Maps and Hash Sets