Data Structures & Algorithms Cheatsheet

Hash Maps and Sets

Use this Data Structures & Algorithms reference while you build software engineering projects, review code, or refresh the syntax you reach for most.

Complexity

A hash table trades memory for O(1) average lookup. Worst case degrades to O(n) when every key lands in the same bucket.

OperationAverageWorstNotes
insertO(1)O(n)amortized, resizes double capacity
lookup by keyO(1)O(n)worst is full collision
deleteO(1)O(n)
iterate allO(n)O(n)order is insertion order in Python 3.7+
min or max keyO(n)O(n)use a heap or sorted structure
range queryO(n)O(n)hash maps cannot do ranges

Space is O(n) plus load-factor slack, roughly 1.5–3× the raw key and value bytes.

When To Reach For A Hash Map

  • You are about to write a nested loop that asks "does this other element exist" for each element. That inner loop becomes a set lookup, turning O(n²) into O(n).
  • You need counts, groupings, or last-seen indices.
  • You need to dedupe or check membership.
  • You need a complement lookup: for target − x, previous prefix sum, or a canonical key like the sorted characters of a word.

Do not reach for it when you need order, ranges, or the k smallest. Those want a sorted array, a heap, or a balanced tree.

Python Dict And Set

d = {}
d["a"] = 1
d.setdefault("b", []).append(2)   # init-on-first-use
d.get("z", 0)                     # default instead of KeyError
d.pop("a", None)                  # delete with default
"a" in d                          # key membership, O(1)
d.keys(), d.values(), d.items()
for k, v in d.items(): ...
{k: v for k, v in d.items() if v > 1}
max(d, key=d.get)                 # key with the largest value

from collections import defaultdict, Counter
groups = defaultdict(list)
groups[key].append(x)             # no setdefault needed
cnt = defaultdict(int)
cnt[x] += 1

c = Counter("mississippi")
c.most_common(2)                  # [('i', 4), ('s', 4)]
c["z"]                            # 0, does not insert
c1 - c2                           # subtract, drops non-positive
c1 & c2                           # per-key minimum (intersection)
sum(c.values())                   # total count

s = set()
s.add(1); s.discard(9)            # discard never raises
s.remove(1)                       # KeyError if absent
a | b, a & b, a - b, a ^ b        # union, intersection, difference, symmetric
a <= b                            # subset
frozenset([1, 2])                 # hashable, usable as a dict key

Key Design

Keys must be hashable and immutable. That constraint drives most of the trick.

Grouping goalKey expression
anagrams"".join(sorted(word)) or a 26-length count tuple
points(x, y) tuple, never a list
grid cell(r, c) or r * cols + c
sets as keysfrozenset(items)
shifted string patternstuple of consecutive character gaps
board statetuple(map(tuple, grid))
case-insensitives.casefold()
# group anagrams in O(n * k log k)
from collections import defaultdict
groups = defaultdict(list)
for w in words:
    groups[tuple(sorted(w))].append(w)
result = list(groups.values())

# O(n * k) variant with a count signature
def sig(w):
    c = [0] * 26
    for ch in w:
        c[ord(ch) - 97] += 1
    return tuple(c)

Canonical Patterns

def two_sum(nums, target):
    """Complement lookup. O(n) time, O(n) space."""
    seen = {}                      # value -> index
    for i, x in enumerate(nums):
        if target - x in seen:
            return [seen[target - x], i]
        seen[x] = i
    return []

def subarray_sum_equals_k(nums, k):
    """Prefix sum counts. Handles negatives, unlike sliding window."""
    from collections import defaultdict
    count = defaultdict(int)
    count[0] = 1                   # empty prefix
    total = ans = 0
    for x in nums:
        total += x
        ans += count[total - k]
        count[total] += 1
    return ans

def longest_consecutive(nums):
    """O(n): only start counting from a sequence start."""
    s = set(nums)
    best = 0
    for x in s:
        if x - 1 in s:
            continue
        y = x
        while y + 1 in s:
            y += 1
        best = max(best, y - x + 1)
    return best

def first_duplicate(a):
    seen = set()
    for x in a:
        if x in seen:
            return x
        seen.add(x)
    return None

Java And C++ Equivalents

Map<String, Integer> m = new HashMap<>();
m.put("a", 1);
m.getOrDefault("z", 0);
m.merge("a", 1, Integer::sum);            // counting idiom
m.computeIfAbsent("k", k -> new ArrayList<>()).add(2);
m.containsKey("a");
for (Map.Entry<String, Integer> e : m.entrySet()) e.getKey();

Set<Integer> s = new HashSet<>();
s.add(1); s.contains(1); s.remove(1);

// ordered variants: O(log n) but give you ranges and min/max
TreeMap<Integer, Integer> t = new TreeMap<>();
t.floorKey(5); t.ceilingKey(5); t.firstKey(); t.lastKey();
t.headMap(5); t.tailMap(5); t.subMap(1, 9);
LinkedHashMap<String, Integer> lru = new LinkedHashMap<>();  // insertion order
unordered_map<string, int> m;
m["a"]++;                      // default-constructs to 0 first
if (m.count("a")) ...
if (auto it = m.find("a"); it != m.end()) it->second;
unordered_set<int> s{1, 2, 3};

map<int, int> t;               // red-black tree, O(log n), sorted
t.lower_bound(5);              // first key >= 5
t.upper_bound(5);              // first key > 5

C++ m["missing"] inserts a default-constructed value. Use .count() or .find() when you only mean to read, especially inside a loop over a const map.

Gotchas

  • Counter[missing] returns 0 without inserting, but defaultdict[missing] inserts. That silently grows the dict and breaks len(d) checks.
  • Mutable keys are a bug. A list or a set cannot be a key. A tuple containing a list cannot either.
  • float keys. 0.1 + 0.2 is not 0.3, and hash(1) == hash(1.0), so {1: "a", 1.0: "b"} has one entry.
  • True and 1 collide. hash(True) == hash(1), so booleans and ints share dict slots.
  • Iteration order. Python dicts and sets: dicts preserve insertion order, sets do not. Java HashMap and C++ unordered_map guarantee nothing.
  • Adversarial hashing. C++ unordered_map<int,int> on competitive judges can be forced to O(n) collisions. Use a custom hash with a random seed, or map.
  • Deleting while iterating raises RuntimeError in Python. Collect keys first: for k in list(d).