Hash tables provide lightning-fast key-value lookups with O(1) average time complexity. They are the engine behind dictionaries, maps, and associative arrays in almost every programming language.
A hash table stores data in key-value pairs. A hash function takes a key and computes an index (a hash code) that determines where the value is stored in an internal array. When you look up a key, the same hash function is used to find its location directly.
A good hash function should be:
def simple_hash(key, table_size):
# Simple hash for string keys
total = 0
for ch in str(key):
total += ord(ch)
return total % table_size
Collisions happen when two different keys hash to the same index. Here are the main strategies:
Each bucket stores a linked list (or another collection) of key-value pairs. When a collision occurs, the new pair is appended to the list at that index.
class HashTableChaining:
def __init__(self, capacity=10):
self.capacity = capacity
self.table = [[] for _ in range(capacity)]
def _hash(self, key):
return hash(key) % self.capacity
def put(self, key, value):
idx = self._hash(key)
for i, (k, v) in enumerate(self.table[idx]):
if k == key:
self.table[idx][i] = (key, value)
return
self.table[idx].append((key, value))
def get(self, key):
idx = self._hash(key)
for k, v in self.table[idx]:
if k == key:
return v
return None
def remove(self, key):
idx = self._hash(key)
self.table[idx] = [(k, v) for k, v in self.table[idx] if k != key]
When a collision occurs, probe for the next empty slot. Variants include:
class HashTableLinearProbing:
def __init__(self, capacity=10):
self.capacity = capacity
self.keys = [None] * capacity
self.values = [None] * capacity
def _hash(self, key):
return hash(key) % self.capacity
def put(self, key, value):
idx = self._hash(key)
while self.keys[idx] is not None:
if self.keys[idx] == key:
self.values[idx] = value
return
idx = (idx + 1) % self.capacity
self.keys[idx] = key
self.values[idx] = value
def get(self, key):
idx = self._hash(key)
while self.keys[idx] is not None:
if self.keys[idx] == key:
return self.values[idx]
idx = (idx + 1) % self.capacity
return None
The load factor is the ratio of stored entries to table capacity. When it exceeds a threshold (typically 0.75), the table is resized (usually doubled in capacity) and all entries are rehashed. This keeps operations fast at the cost of occasional O(n) resizing.
two_sum([2, 7, 11, 15], 9) should return [0, 1].