10 LeetCode Hashing Problems That Build Real Interview Intuition

June 7, 202610 min read
dsaalgorithmsinterview-prepleetcode
10 LeetCode Hashing Problems That Build Real Interview Intuition
TL;DR
  • Hash maps are a choice about what to remember: Two Sum stores indices, Subarray Sum stores prefix counts, 4Sum II stores pair frequencies — the pattern is always pre-compute and look up later.
  • Composite key design unlocks anagram and isomorphism problems: sort characters or canonicalize structure to derive a key that captures equivalence.
  • Prefix count maps cut O(n²) subarray search to O(n): store how many times each prefix sum has appeared, then look up prefix - k at each step.
  • Sliding window with frequency delta compresses two-map comparison into a single satisfied integer, making every window shift O(1) instead of O(k).
  • Bidirectional bijection requires two maps — one in each direction — or problems like Word Pattern silently pass inputs that should fail.
  • Meet-in-the-middle hashing cuts O(n⁴) to O(n²): hash all pairwise sums from two arrays, then look up negations from the remaining two.
  • Array as implicit hash map achieves O(1) extra space when key space is bounded integers — index encodes the key, sign of the stored value encodes presence.

Your first instinct when you see "find two numbers that sum to target" is a nested loop. Don't feel bad. Everyone's first instinct is a nested loop. The nested loop is the developer equivalent of a security blanket.

The real lesson hashing teaches you is not "use a dictionary." It's what you store in it. Two Sum through Minimum Window Substring cover ten distinct techniques. Solve them in order and you'll stop reaching for O(n²) every time the problem says "find all pairs."


1. Two Sum: The Complement Lookup

LeetCode #1 | Easy

The gateway drug. You want two numbers that sum to a target. Brute force is O(n²). Every interviewer has watched a candidate write the brute force and then stare at it like maybe it'll optimize itself.

For each number x, you don't scan the array for target - x. You already stored it. One pass, one lookup.

def twoSum(nums, target): seen = {} for i, x in enumerate(nums): complement = target - x if complement in seen: return [seen[complement], i] seen[x] = i

The foundational move behind almost every hash map optimization: stop scanning, start storing. Everything else in this list builds on it.

Related: LeetCode Two Sum Has Five Solutions


2. Longest Consecutive Sequence: Hash Set Membership

LeetCode #128 | Medium

Given an unsorted array, find the longest sequence of consecutive integers in O(n).

The sort-everything reflex kicks in at O(n log n). The hash set solution is faster because it asks a different question. Don't build the sequence. Check whether you're standing at its start.

def longestConsecutive(nums): num_set = set(nums) best = 0 for n in num_set: if n - 1 not in num_set: length = 1 while n + length in num_set: length += 1 best = max(best, length) return best

The n - 1 not in num_set check prunes the outer loop so each element is visited at most twice total. This is what O(1) membership actually buys you. Not faster inner loops. Fewer of them.


3. Group Anagrams: Composite Key Design

LeetCode #49 | Medium

Group a list of strings by their anagram family.

Two strings are anagrams if and only if they share character frequencies. Sorting gives a canonical form. That sorted string becomes the key. Simple idea. Trips people up anyway.

from collections import defaultdict def groupAnagrams(strs): groups = defaultdict(list) for s in strs: key = tuple(sorted(s)) groups[key].append(s) return list(groups.values())

Most hash map problems hand you the key. This one makes you build it. You see the same idea in isomorphic graphs, canonicalized paths, and equivalent tree problems. The skill is recognizing when the key doesn't exist until you derive it.


4. Subarray Sum Equals K: Prefix Count Map

LeetCode #560 | Medium

Count subarrays that sum to exactly k. Brute force is O(n²). There's a running joke that every medium array problem has an O(n²) brute force and an O(n) solution involving prefix sums. This is that problem.

Store how many times each prefix sum has appeared. When you reach prefix sum p, check how many earlier prefixes had sum p - k. That count is your answer.

from collections import defaultdict def subarraySum(nums, k): counts = defaultdict(int) counts[0] = 1 # empty prefix prefix = 0 result = 0 for x in nums: prefix += x result += counts[prefix - k] counts[prefix] += 1 return result

You're not storing values. You're storing how many times a derived value has appeared. The hash map becomes a frequency table of an intermediate computation. The same pattern appears in contiguous array, count number of nice subarrays, and binary subarrays with sum.

Related: Subarray Sum Equals K: Can You Solve It in 20 Minutes?


5. Find All Anagrams in a String: Sliding Window with Frequency Delta

LeetCode #438 | Medium

Find all starting indices where a permutation of pattern p appears in string s.

The naive approach compares two frequency maps on every window shift. It works. It's also slow, and your interviewer will tilt their head slightly to the left.

Track how many characters are currently "satisfied." Update the satisfied counter on entry and exit. Compare one integer, not two maps.

from collections import Counter def findAnagrams(s, p): need = Counter(p) have = {} satisfied = 0 required = len(need) result = [] left = 0 for right, c in enumerate(s): have[c] = have.get(c, 0) + 1 if c in need and have[c] == need[c]: satisfied += 1 while right - left + 1 > len(p): out = s[left] if out in need and have[out] == need[out]: satisfied -= 1 have[out] -= 1 left += 1 if satisfied == required: result.append(left) return result

This combines sliding window with incremental hash map maintenance. Minimum Window Substring (problem 10) uses the same structure at harder difficulty.

Related: Sliding Window Algorithm


6. Top K Frequent Elements: Frequency Bucketing

LeetCode #347 | Medium

Return the k most frequent elements. The heap solution is O(n log k). There's an O(n) solution using bucket sort, and at least one person in your interview loop knows it.

Build a frequency map, then build an array of buckets indexed by frequency. Scan from the back.

def topKFrequent(nums, k): freq = {} for x in nums: freq[x] = freq.get(x, 0) + 1 buckets = [[] for _ in range(len(nums) + 1)] for x, f in freq.items(): buckets[f].append(x) result = [] for i in range(len(buckets) - 1, -1, -1): result.extend(buckets[i]) if len(result) >= k: return result[:k] return result

The bucket trick uses frequency values as array indices, turning a comparison sort into a counting sort. Instead of storing values as keys, you're storing frequencies as indices. Inverted from the usual direction.


7. Word Pattern: Bidirectional Bijection

LeetCode #290 | Easy

Check whether a pattern like "abba" matches a string like "dog cat cat dog".

The mistake almost everyone makes on their first pass: mapping in only one direction. One map, feeling confident, submitting. Wrong.

def wordPattern(pattern, s): words = s.split() if len(pattern) != len(words): return False char_to_word = {} word_to_char = {} for c, w in zip(pattern, words): if c in char_to_word and char_to_word[c] != w: return False if w in word_to_char and word_to_char[w] != c: return False char_to_word[c] = w word_to_char[w] = c return True

Without both maps, "abba" against "dog dog dog dog" passes incorrectly. One map checks that each pattern character maps to exactly one word. The other checks the reverse. This is bijection. It appears in isomorphic strings, encoding problems, and anywhere one-to-one correspondence matters.


8. 4Sum II: Combinatorial Halving

LeetCode #454 | Medium

Count tuples (i, j, k, l) such that A[i] + B[j] + C[k] + D[l] == 0. Brute force is O(n⁴). Say that out loud. O(n to the fourth). Then realize there's a better way.

Compute all pairwise sums of A and B, store their counts. For every pair in C and D, look up whether its negation exists. O(n²) instead of O(n⁴).

from collections import defaultdict def fourSumCount(A, B, C, D): ab = defaultdict(int) for a in A: for b in B: ab[a + b] += 1 count = 0 for c in C: for d in D: count += ab[-(c + d)] return count

This is meet-in-the-middle via hashing. Split a four-way enumeration in half and hash one half's results. The key: you don't need to know which pairs sum to a value, just how many. Same pattern in XOR target problems and balanced partition counts.


9. First Missing Positive: The Array as Implicit Hash Map

LeetCode #41 | Hard

Find the smallest positive integer not in the array. O(n) time, O(1) extra space.

This one forbids an actual hash map. The constraint sounds cruel until you realize what it's teaching you.

For each value v in [1, n], negate index v-1 to mark it visited. Then scan for the first non-negative entry.

def firstMissingPositive(nums): n = len(nums) for i in range(n): if nums[i] <= 0 or nums[i] > n: nums[i] = n + 1 for i in range(n): v = abs(nums[i]) if 1 <= v <= n: nums[v - 1] = -abs(nums[v - 1]) for i in range(n): if nums[i] > 0: return i + 1 return n + 1

This forces you to understand what a hash map actually is: a mapping from keys to values where the key determines storage location. When key space is bounded integers, an array is a valid hash map. That's why O(1) space is possible here. The constraint isn't cruel. It's clarifying.


10. Minimum Window Substring: The Have/Need Contract

LeetCode #76 | Hard

Find the smallest window in s containing all characters of t.

This is the final boss. Composite state tracking, incremental update, and optimal bookkeeping all running together. If you can explain this under pressure you've internalized the have/need pattern. If you can't, you have a clear thing to practice.

from collections import Counter def minWindow(s, t): need = Counter(t) have = {} satisfied = 0 required = len(need) left = 0 result = "" for right, c in enumerate(s): have[c] = have.get(c, 0) + 1 if c in need and have[c] == need[c]: satisfied += 1 while satisfied == required: window = s[left:right + 1] if not result or len(window) < len(result): result = window out = s[left] have[out] -= 1 if out in need and have[out] < need[out]: satisfied -= 1 left += 1 return result

The satisfied counter compresses a full two-map comparison into a single integer check, making every window operation O(1). Every character enters once and leaves once. That's the contract.


The Pattern Behind LeetCode Hashing Problems

Solve these ten and a theme becomes clear.

A hash map is a choice about what to remember, not just a data structure. Two Sum remembers indices. Subarray Sum Equals K remembers prefix counts. 4Sum II remembers pair sum frequencies. Each problem is asking: what should I pre-compute and store so that later lookups are O(1)?

The other through-line: the hash map almost always stores something derived from the input. Sorted characters. Prefix sums. Pair sums. Frequency deltas. Once you see inputs as raw material for keys and values rather than the keys and values themselves, the patterns become recognizable before you write a line.

ProblemTechnique
Two SumComplement lookup
Longest Consecutive SequenceSet membership for O(n) pruning
Group AnagramsComposite key design
Subarray Sum Equals KPrefix count map
Find All AnagramsSliding window with frequency delta
Top K Frequent ElementsFrequency bucketing
Word PatternBidirectional bijection
4Sum IICombinatorial halving
First Missing PositiveArray as implicit hash map
Minimum Window SubstringHave/need contract

Knowing the solution and explaining it under pressure are different skills. If you want to practice the second one, SpaceComplexity runs voice-based mock interviews with rubric-based feedback. Hashing problems appear in every session. Try explaining the satisfied counter cold and see where you lose the thread.


Further Reading