| Problem Signal | Technique |
|---|---|
| Find pair with sum = k | Two sum pattern (hash table stores complement) |
| Find pair with (sum/product/diff) % k = 0 | Two sum variant (hash table stores modulo) |
| Count pairs/triplets/tuples with condition | N-sum pattern (reduce to smaller subproblems) |
| Group elements by property (anagram, pattern) | Grouping pattern (use tuple/string as key) |
| Count frequency / find duplicates | Counter (frequency counting) |
| Track most recent occurrence | Hash table stores index |
| Bidirectional mapping (isomorphic) | Two hash tables (forward + backward) |
| Clone graph/linked list with random pointers | Hash table maps old node to new node |
| Find longest consecutive sequence | Union-find via hash table (store endpoints) |
| Compute slope/ratio between points | Normalize with gcd, use tuple as key |
| LRU cache / insertion order matters | dict (Python 3.7+ preserves insertion order) |
| OrderedDict needed (move_to_end) | OrderedDict for explicit ordering operations |
| Default values for missing keys | defaultdict(int/list/set) |
Classic pattern: find pair where A[i] + A[j] = target. Store complement in hash table as you iterate.
LC 1, 167
def twoSum(self, A: List[int], target: int) -> List[int]:
seen = {}
for i, x in enumerate(A):
if x in seen:
return [seen[x], i]
seen[target - x] = iLC 1010, 1497
If (x + y) % k = 0, then y % k = (-x % k) % k = (k - x % k) % k.
def numPairsDivisibleByK(self, A: List[int], k: int) -> int:
cnt = Counter()
ans = 0
for x in A:
x %= k
ans += cnt[(k - x) % k]
cnt[x] += 1
return ansCount all pairs, not just find one.
cnt = Counter(A)
ans = 0
for x in cnt:
complement = target - x
if complement in cnt:
if x == complement:
# same element: choose 2 from cnt[x]
ans += cnt[x] * (cnt[x] - 1) // 2
elif x < complement:
# avoid double counting
ans += cnt[x] * cnt[complement]
return ansReduce N-sum to (N-1)-sum by fixing one element, then use hash table for the innermost level.
LC 454
Split into two 2-sums. Compute all A[i] + B[j], store frequency in hash table, then check C[k] + D[l].
def fourSumCount(self, A, B, C, D):
cnt = Counter()
for a in A:
for b in B:
cnt[a + b] += 1
ans = 0
for c in C:
for d in D:
ans += cnt[-(c + d)]
return ansLC 923
Count triplets where A[i] + A[j] + A[k] = target (i < j < k).
def threeSumMulti(self, A, target):
cnt = Counter(A)
ans = 0
for i, x in enumerate(sorted(cnt)):
for j, y in enumerate(sorted(cnt)):
if j < i:
continue
z = target - x - y
if z < y or z not in cnt:
continue
if x == y == z:
# all three same
ans += cnt[x] * (cnt[x] - 1) * (cnt[x] - 2) // 6
elif x == y:
# two same
ans += cnt[x] * (cnt[x] - 1) // 2 * cnt[z]
elif y == z:
# two same
ans += cnt[x] * cnt[y] * (cnt[y] - 1) // 2
elif x < y < z:
# all different
ans += cnt[x] * cnt[y] * cnt[z]
return ans % (10**9 + 7)Use hash table to group elements by some normalized key.
LC 49
def groupAnagrams(self, words):
groups = defaultdict(list)
for w in words:
key = tuple(sorted(w)) # or: ''.join(sorted(w))
groups[key].append(w)
return list(groups.values())LC 205, 290
Use two hash tables to ensure bijection (one-to-one mapping).
def isIsomorphic(self, s: str, t: str) -> bool:
if len(s) != len(t):
return False
s2t, t2s = {}, {}
for i, j in zip(s, t):
if i in s2t and s2t[i] != j:
return False
if j in t2s and t2s[j] != i:
return False
s2t[i] = j
t2s[j] = i
return TrueLC 1072
Rows that are equal or complementary can all become equal after column flips. Normalize each row to canonical form.
def maxEqualRowsAfterFlips(self, matrix):
cnt = Counter()
for row in matrix:
# normalize: flip row if it starts with 1
key = tuple(row if row[0] == 0 else [1 - x for x in row])
cnt[key] += 1
return max(cnt.values())LC 149
For each point, compute slope to all other points. Normalize slope as (dy/gcd, dx/gcd).
def maxPoints(self, points):
ans = 0
for i, (x1, y1) in enumerate(points):
slopes = defaultdict(int)
for j, (x2, y2) in enumerate(points):
if i == j:
continue
if x1 == x2:
slopes[(inf, inf)] += 1
else:
dx, dy = x1 - x2, y1 - y2
g = gcd(dx, dy)
slopes[(dy // g, dx // g)] += 1
ans = max(ans, 1 + max(slopes.values(), default=0))
return ansUse Counter for frequency-based queries.
Counter.most_common(num): return a list contains tuple.del Counter[key]: delete an item. orCounter[key]=0.cntA+cntB: add two counters together.cntA-cntB: subtract (keeping only positive counts).cntA&cntB: find intersection of two counters. min(cntA, cntB)cntA|cntB: find union of two counters. max(cntA, cntB)
LC 1002
Find characters that appear in all strings with their minimum frequency.
def commonChars(self, words):
ans = Counter(words[0])
for w in words:
ans &= Counter(w) # intersection
return list(ans.elements())LC 692
def topKFrequent(self, words, k):
return [w for w, _ in sorted(Counter(words).most_common(),
key=lambda t: (-t[1], t[0]))][:k]LC 954
Check if array can be paired such that one element is double of another.
def canReorderDoubled(self, arr):
cnt = Counter(arr)
for x in sorted(cnt, key=abs):
if cnt[x] > cnt[2 * x]:
return False
cnt[2 * x] -= cnt[x]
return TrueStore index in hash table to find minimum/maximum distance between duplicates.
LC 2260, 219
def minimumCardPickup(self, cards):
ans = inf
last = {}
for i, c in enumerate(cards):
if c in last:
ans = min(ans, i - last[c] + 1)
last[c] = i
return ans if ans < inf else -1LC 128
Use hash table to store sequence endpoints. When inserting x, check if x-1 and x+1 exist, then merge sequences.
def longestConsecutive(self, nums):
mp = {}
for x in nums:
if x not in mp:
l = mp.get(x - 1, 0)
r = mp.get(x + 1, 0)
length = l + r + 1
mp[x] = mp[x - l] = mp[x + r] = length
return max(mp.values(), default=0)LC 133, 138
Use hash table to map original nodes to copied nodes.
def copyRandomList(self, head):
if not head:
return None
# first pass: create all nodes
seen = {}
curr = head
while curr:
seen[curr] = Node(curr.val)
curr = curr.next
# second pass: link next and random pointers
curr = head
while curr:
if curr.next:
seen[curr].next = seen[curr.next]
if curr.random:
seen[curr].random = seen[curr.random]
curr = curr.next
return seen[head]LC 146
Python 3.7+ dict preserves insertion order. Use pop + re-insert to move to end.
class LRUCache:
def __init__(self, capacity: int):
self.cache = {}
self.cap = capacity
def get(self, key: int) -> int:
if key in self.cache:
val = self.cache.pop(key)
self.cache[key] = val # move to end
return val
return -1
def put(self, key: int, value: int) -> None:
if key in self.cache:
self.cache.pop(key)
elif len(self.cache) == self.cap:
# evict least recently used (first item)
self.cache.pop(next(iter(self.cache)))
self.cache[key] = valueUse defaultdict when you need automatic initialization of missing keys.
groups = defaultdict(list)
for item in items:
key = compute_key(item)
groups[key].append(item)freq = defaultdict(lambda: defaultdict(int))
for item in items:
category = get_category(item)
freq[category][item] += 1graph = defaultdict(list)
for u, v in edges:
graph[u].append(v)
graph[v].append(u)Use OrderedDict when you need explicit control over insertion order (move_to_end, popitem).
from collections import OrderedDict
class LRUCache:
def __init__(self, capacity: int):
self.cache = OrderedDict()
self.cap = capacity
def get(self, key: int) -> int:
if key in self.cache:
self.cache.move_to_end(key)
return self.cache[key]
return -1
def put(self, key: int, value: int) -> None:
if key in self.cache:
self.cache.move_to_end(key)
self.cache[key] = value
if len(self.cache) > self.cap:
self.cache.popitem(last=False)-
Hash table = O(1) lookup: The core advantage. Use it whenever you need to check existence or retrieve value in constant time.
-
Complement pattern: Instead of storing
x, storetarget - x. This transforms "find two elements" into "check if one element exists". -
Normalize keys: For grouping, normalize the key to canonical form (sorted tuple, gcd-reduced ratio, etc.) so equivalent items map to same key.
-
Two hash tables for bijection: When you need one-to-one mapping (isomorphic, word pattern), use two hash tables to check both directions.
-
Counter arithmetic: Use Counter's built-in operations (
+,-,&,|) for set-like operations on multisets. -
Index as value: Store indices (not just existence) to compute distances, ranges, or track first/last occurrence.
-
Reduce N-sum to 2-sum: Split N-sum problems into nested loops with hash table at the innermost level. For 4-sum, compute all 2-sums first, store in hash table, then check remaining 2-sums.
-
Frequency map + sorted iteration: When pairing elements with constraints (like doubling), iterate in sorted order by key and greedily consume from frequency map.
-
Python dict is ordered (3.7+): You can use plain dict for LRU cache. Use OrderedDict only if you need
move_to_endorpopitem(last=False). -
defaultdict saves boilerplate: Use
defaultdict(int)for counting,defaultdict(list)for grouping,defaultdict(set)for deduplication.