Pattern 03: Hashing / Frequency Maps
Pattern guideUpdated
On this page
- 0. The Template (Copy-Paste Skeleton)
- The three seeds people forget
- Java API cheat sheet
- 1. Pattern Identification & Logic
- How to Identify (Interview Triggers)
- The Algorithm (Pseudocode)
- The ‘Trick’ to Know
- 2. Java Implementation (Brute Force vs. Optimal)
- Example Problem: Two Sum - LC 1
- Java Architecture Insights
- 3. Mental Model & Visualization
- ASCII Diagram (Two Sum: nums=[2,7,11,15], target=9)
- Senior Mental Trigger
- 4. Curated Problem Lists
- Commonly Asked (Bread & Butter)
- FAANG ‘Aha!’ Level (Hard/Unintuitive)
- 5. Time & Space Complexity Table
0. The Template (Copy-Paste Skeleton)
Three skeletons cover nearly every hashing problem: count, seen-before, and prefix-sum + map.
// TEMPLATE A — FREQUENCY COUNT
Map<Character, Integer> freq = new HashMap<>();
for (char c : s.toCharArray()) {
freq.merge(c, 1, Integer::sum); // or: freq.put(c, freq.getOrDefault(c, 0) + 1)
}
// lowercase-only alphabet? use int[26] — faster, no boxing, no hashing:
int[] count = new int[26];
for (char c : s.toCharArray()) count[c - 'a']++;
// TEMPLATE B — "HAVE I SEEN THE COMPLEMENT?" (one pass, Two-Sum shape)
Map<Integer, Integer> seen = new HashMap<>(); // value -> index
for (int i = 0; i < nums.length; i++) {
int need = target - nums[i];
if (seen.containsKey(need)) return new int[]{seen.get(need), i};
seen.put(nums[i], i); // put AFTER the check — avoids matching itself
}
// TEMPLATE C — PREFIX SUM + MAP (subarrays summing to k)
Map<Integer, Integer> prefixCount = new HashMap<>();
prefixCount.put(0, 1); // CRITICAL seed: the empty prefix
int sum = 0, result = 0;
for (int x : nums) {
sum += x;
result += prefixCount.getOrDefault(sum - k, 0); // count subarrays ending here
prefixCount.merge(sum, 1, Integer::sum);
}
// TEMPLATE D — GROUP BY A CANONICAL KEY (anagrams, patterns)
Map<String, List<String>> groups = new HashMap<>();
for (String w : words) {
char[] ch = w.toCharArray(); Arrays.sort(ch);
groups.computeIfAbsent(new String(ch), k -> new ArrayList<>()).add(w);
}
The three seeds people forget
prefixCount.put(0, 1)— without it you miss every subarray that starts at index 0.- Put after check in Template B, or
nums[i]matches itself whentarget == 2*nums[i]. computeIfAbsentreturns the list, so you can.add(...)in the same expression.
Java API cheat sheet
| Want | Use |
|---|---|
| increment a count | map.merge(k, 1, Integer::sum) |
| default when missing | map.getOrDefault(k, 0) |
| lazily create a bucket | map.computeIfAbsent(k, x -> new ArrayList<>()) |
| set semantics + “was it new?” | set.add(x) returns false if already present |
| keys in sorted order | TreeMap — O(log n) ops, but gives floorKey/ceilingKey |
1. Pattern Identification & Logic
How to Identify (Interview Triggers)
- “Find if two elements satisfy a condition” (unsorted input)
- “Group elements by a property” (anagrams, frequency)
- “Count occurrences” or “frequency” mentioned
- “Find duplicates” or “first unique” element
- Need O(1) lookup to avoid nested loops
The Algorithm (Pseudocode)
Pattern A: Complement Lookup (Two Sum style)
map = empty HashMap
for each (index, value) in array:
complement = target - value
if complement in map:
return [map[complement], index]
map[value] = index
Pattern B: Frequency Count
freqMap = empty HashMap
for each element:
freqMap[element]++
process freqMap for the answer
The ‘Trick’ to Know
- HashMap load factor: Java’s default is 0.75. When 75% full, it resizes (doubles + rehashes all entries). Pre-sizing with
new HashMap<>(expectedSize * 4 / 3 + 1)avoids mid-operation resizing. - For character frequency with known ASCII range,
int[26]orint[128]is faster than HashMap.
2. Java Implementation (Brute Force vs. Optimal)
Example Problem: Two Sum - LC 1
Brute Force: O(n^2)
class Solution {
public int[] twoSum(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[i] + nums[j] == target) {
return new int[]{i, j};
}
}
}
return new int[]{};
}
}
Optimal: HashMap Complement Lookup - O(n)
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> seen = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
if (seen.containsKey(complement)) {
return new int[]{seen.get(complement), i};
}
seen.put(nums[i], i);
}
return new int[]{};
}
}
Java Architecture Insights
Map.getOrDefault(key, 0): Eliminates null checks when counting frequencies.computeIfAbsent(key, k -> new ArrayList<>()): Perfect for grouping problems (Group Anagrams). Creates the list only if key is absent.- Why
HashMapoverTreeMap? HashMap gives O(1) average. TreeMap gives O(log n) but maintains sorted order - only use it when you need sorted keys.
3. Mental Model & Visualization
ASCII Diagram (Two Sum: nums=[2,7,11,15], target=9)
Step 1: num=2, complement=7, map={} → not found, store {2:0}
Step 2: num=7, complement=2, map={2:0} → FOUND! return [0, 1]
Senior Mental Trigger
“Need O(1) lookup to avoid nested loop = HashMap.”
4. Curated Problem Lists
Commonly Asked (Bread & Butter)
| # | Problem | Difficulty |
|---|---|---|
| LC 1 | Two Sum | Easy |
| LC 242 | Valid Anagram | Easy |
| LC 217 | Contains Duplicate | Easy |
| LC 383 | Ransom Note | Easy |
| LC 560 | Subarray Sum Equals K | Medium |
FAANG ‘Aha!’ Level (Hard/Unintuitive)
| # | Problem | Difficulty |
|---|---|---|
| LC 49 | Group Anagrams | Medium |
| LC 128 | Longest Consecutive Sequence | Medium |
| LC 347 | Top K Frequent Elements | Medium |
| LC 438 | Find All Anagrams | Medium |
| LC 76 | Minimum Window Substring | Hard |
5. Time & Space Complexity Table
| Approach | Time | Space | Notes |
|---|---|---|---|
| Brute Force | O(n^2) | O(1) | Nested loop for all pairs |
| HashMap | O(n) | O(n) | Single pass with complement lookup |
| Sorting + Two Ptr | O(n log n) | O(1) | Loses original indices |