Pattern family: Frequency / Lookup / Grouping
Difficulty range: Easy β Hard
Language: Java
- How to identify this pattern β start here
- What is this pattern?
- Core rules
- 2-Question framework
- Variants table
- Universal Java template
- Quick reference cheatsheet
- Common mistakes
- Complexity summary
Read this first. Recognizing the pattern is more important than memorizing the solution.
Use this 3-layer system: scan keywords β test the brute force β confirm the signal.
Certain words almost always mean HashMap. Scan for these first.
| If you see this word/phrase... | It likely means... | HashMap role |
|---|---|---|
| "two numbers that sum to target" | complement lookup | store value β index |
| "find a pair with difference k" | complement lookup | store value, check value + k |
| "count occurrences / frequency" | frequency map | store value β count |
| "most frequent / top k" | frequency map + sort | store value β count |
| "first non-repeating / unique" | frequency map | count β find first with count 1 |
| "duplicate / already seen" | seen set/map | set.contains(x) |
| "anagram / permutation of" | frequency map | compare char counts |
| "group by / classify" | bucketing | canonical key β list |
| "subarray with sum k" | prefix sum map | store prefixSum β count |
| "longest subarray / substring" | prefix sum or sliding window map | store prefixSum β first index |
| "can X be constructed from Y" | two-map compare | check supply β₯ demand |
| "isomorphic / pattern match" | two-map bidirectional | map both directions |
| "consecutive sequence / streak" | HashSet | O(1) membership, count from starts only |
| "recently used / LRU cache" | LinkedHashMap or HashMap + DLL | access-order eviction |
| "jewels in stones / membership" | HashSet lookup | add jewels, check each stone |
| "nearby duplicate / within k" | index tracking map | value β last index seen |
Write the brute force in your head. If it has a nested loop, HashMap probably removes one loop.
Brute force has nested loop?
β
βββ YES β Inner loop is "searching for something"?
β β
β βββ YES β HashMap can replace the inner search β O(n)
β βββ NO β Maybe sorting or two pointers instead
β
βββ NO β Maybe HashMap is not needed (already O(n) or O(log n))
Example:
Problem: "Find if any two numbers sum to target"
Brute force:
for i in 0..n
for j in i+1..n β inner loop = searching for complement
if nums[i]+nums[j]==target β return true
Inner loop just searches β replace with HashMap lookup β O(n)
After keywords and brute force, confirm with these signals:
Signal 1 β You need O(n) but brute force is O(nΒ²)
The problem says "optimize" or has n β€ 10^5 (O(nΒ²) would TLE). HashMap is the standard way to go from O(nΒ²) β O(n).
Signal 2 β You need to "remember" something from earlier in the array
"Have I seen x before?", "What index was x at?", "How many times did x appear before position i?"
All of these = store in HashMap as you go.
Signal 3 β Two things need to be matched or compared
ransomNote β magazine, pattern β string, s β t for isomorphic
Two separate entities being compared = two frequency maps.
Signal 4 β The answer depends on a count or frequency
"how many subarrays", "how many pairs", "most/least frequent"
Counting = HashMap values are counts.
Signal 5 β Elements need to be grouped by a property
"group anagrams", "group by first letter", "classify by remainder"
Grouping = HashMap values are lists, key is the canonical property.
Read the problem
β
βΌ
See "pair / sum / complement"? ββYESβββΆ Complement lookup
β map: value β index
NO
β
βΌ
See "count / frequency / most"? ββYESβββΆ Frequency map
β map: value β count
NO
β
βΌ
See "duplicate / seen / exists"? ββYESβββΆ HashSet (or map with bool)
β set: add and check
NO
β
βΌ
See "group / anagram / classify"?ββYESβββΆ Bucketing
β map: canonical key β list
NO
β
βΌ
See "subarray / substring" + ββYESβββΆ Prefix sum + HashMap
"sum = k / longest"? map: prefixSum β count/index
β
NO
β
βΌ
See "sliding window" + ββYESβββΆ Window frequency map
"at most k distinct"? map: char β count in window
β
NO
β
βΌ
Brute force has nested loop
where inner loop "searches"? ββYESβββΆ HashMap likely removes inner loop
β
NO
β
βΌ
Probably not a HashMap problem
(try sorting / two pointers / binary search)
Sometimes two problems look identical but need different approaches. HashMap is not always the answer.
| Problem | Looks like HashMap? | Actually needs | Why |
|---|---|---|---|
| Two Sum (unsorted) | YES | HashMap | O(n), single pass |
| Two Sum (sorted array) | maybe | Two pointers | O(n) with no extra space |
| Contains Duplicate | YES | HashSet | no value to store, just existence |
| Longest Consecutive Sequence | YES | HashSet | set for O(1) lookup, no counts |
| Subarray Sum = K | YES | Prefix sum HashMap | need running total |
| Maximum Subarray (Kadane's) | NO | DP (Kadane) | no lookup needed |
| Anagram in string (sliding) | YES | Sliding window + freq map | fixed-size window |
| Valid Anagram (whole string) | YES | Single freq map | no window needed |
1. "Given an array, return true if any value appears at least twice."
Signal: ____________ Pattern: ____________
2. "Given strings s and t, return true if t is an anagram of s."
Signal: ____________ Pattern: ____________
3. "Find the number of subarrays that sum to k."
Signal: ____________ Pattern: ____________
4. "Given a list of words, group all anagrams together."
Signal: ____________ Pattern: ____________
5. "Find the longest substring where no character repeats."
Signal: ____________ Pattern: ____________
Answers
1. "appears at least twice" β duplicate/seen β HashSet
2. "anagram" β frequency map β map char counts of s, compare with t
3. "subarrays that sum to k" β prefix sum β map prefixSum β count
4. "group all anagrams" β bucketing β map sorted(word) β list
5. "longest substring, no rep" β sliding window β map char β last index
One-line rule to remember:
If the brute force searches inside a loop β HashMap kills that search. If you need to remember what you've seen β HashMap stores it.
The HashMap pattern trades extra memory for speed. Instead of comparing every element against every other element (O(nΒ²)), you store elements in a map as you process them and look up what you need in O(1) time.
Core idea in one line:
"Store what you've seen. Ask the map instead of re-scanning the array."
Why it works:
A HashMap gives you O(1) average-time put, get, and containsKey. Any problem that naively needs a nested loop β searching for a complement, counting occurrences, grouping items β can be solved in a single pass with a HashMap.
Brute force: for each x β scan entire array for answer β O(nΒ²)
HashMap: for each x β put(x); look up answer in map β O(n)
Rule 1 β Look up BEFORE you store (Two Sum rule)
// WRONG β stores first, then finds: may match element with itself
map.put(nums[i], i);
if (map.containsKey(target - nums[i])) ... // BUG
// CORRECT β check first, then store
if (map.containsKey(target - nums[i])) return ...;
map.put(nums[i], i);Rule 2 β Always initialize base cases for prefix-sum maps
map.put(0, 1); // prefix sum problems: "empty prefix" exists once
map.put(0, -1); // longest subarray problems: index before startForgetting this causes wrong answers on inputs where the answer starts at index 0.
Rule 3 β char[] cannot be a HashMap key directly
char[] arr = s.toCharArray();
Arrays.sort(arr);
// WRONG β compares references, not content
map.put(arr, list);
// CORRECT β convert to String first
map.put(new String(arr), list);
map.put(Arrays.toString(arr), list);Ask these two questions to identify which variant you need:
| Answer | Store | Example |
|---|---|---|
| How many times does X appear? | value β count |
Valid Anagram, Top K |
| Where did I first see X? | value β index |
Two Sum, Longest Subarray |
| Which group does X belong to? | canonical key β [items] |
Group Anagrams |
| Have I seen X before? | value β boolean (use Set) |
Contains Duplicate |
| Running total up to here? | prefixSum β count/index |
Subarray Sum = K |
| Answer | Operation | Example |
|---|---|---|
| Does the complement exist? | containsKey(target - x) |
Two Sum |
| How many times have I seen X? | getOrDefault(x, 0) |
Frequency problems |
| What's the earliest index of X? | get(x) β compute distance |
Longest Subarray |
| What group does X map to? | computeIfAbsent(key, ...) |
Group Anagrams |
| Can I build this from that? | compare two maps | Ransom Note |
Decision shortcut:
- "find pair/sum" β complement lookup
- "count/frequency" β frequency map
- "duplicate/seen" β HashSet
- "group/classify" β bucketing
- "longest subarray" β prefix sum + map
One thing is common across all 5 patterns:
HashMap<K, V> map = new HashMap<>()
What differs: What is the Key β What is the Value β What do you query from the map
Decide these three things first β the code writes itself.
| Pattern | Key | Value | What you ask the map | Classic problems |
|---|---|---|---|---|
| Frequency count | element | count | how many times did x appear? | Valid Anagram, Top K, Balloons |
| Complement lookup | value | index | does target-x exist? | Two Sum, 4Sum II |
| Prefix sum | prefixSum | count / first index | was prefix-k seen before? | Subarray Sum=K, Longest Subarray |
| Grouping | canonical form | List<item> |
who belongs to this group? | Group Anagrams, Classify |
| Index tracking | element | last index seen | where was x last seen? shrink window | Longest Substr No Repeat |
| Two-map bidirectional | element from A | element from B | is mapping consistent both ways? | Isomorphic, Word Pattern |
| HashSet membership | element | β (just existence) | is x a jewel / duplicate / consecutive? | Jewels, Contains Dup, Consecutive |
| LRU / Design | key | node / value | get O(1), evict LRU on overflow | LRU Cache |
When to use: "count occurrences", "how many times", "anagram", "most frequent"
Key = element itself (char, int, string)
Value = how many times it has been seen
Query = map.get(x) β compare or check the count
Map<Character, Integer> freq = new HashMap<>();
// Build the frequency map
for (char c : s.toCharArray())
freq.put(c, freq.getOrDefault(c, 0) + 1);
// Query β read the count
freq.get('a'); // how many times 'a' appeared
freq.getOrDefault('z', 0); // safe read β returns 0 if absentProblems: Valid Anagram, Top K Frequent, Max Balloons, Ransom Note, First Non-Repeating, Longest Palindrome
When to use: "two numbers that sum to target", "find pair with difference k"
Key = the number itself
Value = its index in the array
Query = map.containsKey(target - x) β look for the complement
Map<Integer, Integer> seen = new HashMap<>(); // value β index
for (int i = 0; i < nums.length; i++) {
int complement = target - nums[i];
// CHECK first β STORE after (prevents matching an element with itself)
if (seen.containsKey(complement))
return new int[]{seen.get(complement), i};
seen.put(nums[i], i);
}Problems: Two Sum, 4Sum II, Pair with given difference
When to use: "subarray sum equals k", "longest subarray with sum k", "count subarrays with condition"
Key = running prefix sum
Value = count (for "how many subarrays") OR first index (for "longest subarray")
Query = map.get(prefix - k) β find a previous prefix that creates a subarray summing to k
// Variant A β COUNT of subarrays with sum = k
Map<Integer, Integer> prefixCount = new HashMap<>();
prefixCount.put(0, 1); // CRITICAL base case β empty prefix seen once
int prefix = 0, count = 0;
for (int n : nums) {
prefix += n;
count += prefixCount.getOrDefault(prefix - k, 0); // query
prefixCount.put(prefix, prefixCount.getOrDefault(prefix, 0) + 1);
}
// Variant B β LONGEST subarray with sum = k
Map<Integer, Integer> firstSeen = new HashMap<>();
firstSeen.put(0, -1); // base case β index before array start
int prefix = 0, maxLen = 0;
for (int i = 0; i < nums.length; i++) {
prefix += nums[i];
if (firstSeen.containsKey(prefix - k))
maxLen = Math.max(maxLen, i - firstSeen.get(prefix - k));
firstSeen.putIfAbsent(prefix, i); // store only the first occurrence
}Problems: Subarray Sum = K, Longest Subarray Sum K, Contiguous Array (0s and 1s)
When to use: "group by", "classify", "anagram groups", "items with the same property together"
Key = canonical form (sorted string, remainder, first letter β whatever the shared property is)
Value = list of all items that share this key
Query = map.get(key) β retrieve the entire group
Map<String, List<String>> groups = new HashMap<>();
for (String s : strs) {
// Step 1: compute the canonical key β the shared property
char[] arr = s.toCharArray();
Arrays.sort(arr);
String key = new String(arr); // "eat", "tea", "ate" all become "aet"
// Step 2: add to that group's bucket
groups.computeIfAbsent(key, k -> new ArrayList<>()).add(s);
}
return new ArrayList<>(groups.values());Problems: Group Anagrams, Group by remainder, Classify strings by pattern
When to use: "longest substring without repeating characters", "first non-repeating", "shrink window on duplicate"
Key = element itself
Value = last index where it was seen
Query = map.get(x) β where was x last seen? β update left pointer
Map<Character, Integer> lastSeen = new HashMap<>(); // char β last index seen
int left = 0, maxLen = 0;
for (int right = 0; right < s.length(); right++) {
char c = s.charAt(right);
if (lastSeen.containsKey(c)) {
// Duplicate found β shrink the window from the left
// Math.max ensures left never moves backwards
left = Math.max(left, lastSeen.get(c) + 1);
}
lastSeen.put(c, right); // always update to the latest index
maxLen = Math.max(maxLen, right - left + 1);
}Problems: Longest Substring No Repeat, Longest Substring K Distinct, First Non-Repeating
"What am I storing as the KEY
and what am I storing as the VALUE?"
KEY = the thing you are searching for or grouping by
VALUE = what you need to remember (count / index / list)
Once these two are clear β the pattern is clear β the code follows.
// βββ SETUP ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Map<KeyType, ValueType> map = new HashMap<>();
// Base case β only for prefix-sum variants
// map.put(0, 1); // for count-based prefix sum
// map.put(0, -1); // for index-based (longest subarray) prefix sum
// βββ SINGLE PASS ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
for (ElementType x : data) {
// Step 1: transform x into a key (identity / sort / prefix sum / mod β¦)
KeyType key = transform(x);
// Step 2: ask the map β O(1) lookup
if (map.containsKey(key)) {
answer = combine(map.get(key), x); // found complement / group / duplicate
}
// Step 3: store AFTER asking (Two Sum rule β prevents self-match)
map.put(key, store(x)); // store value, index, count, list β¦
}
// βββ QUERY THE MAP ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
// Post-loop aggregation if needed (e.g. palindrome length, top-k)
for (Map.Entry<KeyType, ValueType> entry : map.entrySet()) {
// process entry.getKey(), entry.getValue()
}Java utility methods you will use constantly:
map.getOrDefault(key, 0) // safe get with default
map.put(key, map.getOrDefault(key, 0) + 1) // increment frequency
map.computeIfAbsent(key, k -> new ArrayList<>()).add(item) // grouping
map.putIfAbsent(key, value) // store only first occurrence
map.merge(key, 1, Integer::sum) // another way to incrementSIGNAL IN PROBLEM β PATTERN β KEY OPERATION
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
"find two numbers that sum to" β complement lookup β map.containsKey(target - x)
"count occurrences / frequency" β frequency map β map.getOrDefault(x, 0) + 1
"find duplicate / contains" β HashSet β set.contains(x)
"group / classify / anagram" β bucketing β map.computeIfAbsent(key, ...)
"longest subarray with sum k" β prefix sum + map β map.get(prefix - k)
"first non-repeating" β freq map, loop twice β freq.get(c) == 1
"can X be built from Y" β two-map compare β magFreq[c] >= noteFreq[c]
"palindrome length" β freq map + math β even counts + 1 center
"sliding window + constraint" β window freq map β window.size() > k β shrink
"isomorphic / word pattern" β two-map bidirectional β check sToT and tToS both
"jewels in stones / membership" β HashSet lookup β set.contains(stone)
"consecutive sequence O(n)" β HashSet + start check β only count from num-1 not in set
"nearby duplicate within k" β index tracking map β i - lastIndex[val] <= k
"LRU cache / evict oldest" β LinkedHashMap or DLL β access-order, removeEldestEntry
Java API quick-pick:
map.put(k, map.getOrDefault(k, 0) + 1); // increment count
map.merge(k, 1, Integer::sum); // same, more concise
map.computeIfAbsent(key, x -> new ArrayList<>()).add(item); // grouping
map.putIfAbsent(key, value); // store first occurrence only
map.getOrDefault(key, defaultValue); // safe get
map.forEach((k, v) -> { ... }); // iterate
// When you need sorted keys β TreeMap
// When you need insert order β LinkedHashMap (e.g. LRU cache)// BUG: nums=[3,3], target=6 β wrongly returns [0,0]
map.put(nums[i], i);
if (map.containsKey(target - nums[i])) ... // WRONG
// FIX: check first, store after
if (map.containsKey(target - nums[i])) return new int[]{map.get(...), i};
map.put(nums[i], i); // CORRECTMap<Integer, Integer> map = new HashMap<>(); // WRONG β misses subarrays from index 0
map.put(0, 1); // CORRECT for count problems
map.put(0, -1); // CORRECT for "longest subarray" (index before array start)map.put(arr, value); // WRONG β compares object references
map.put(new String(arr), value); // CORRECT
map.put(Arrays.toString(arr), value); // CORRECTleft = lastSeen.get(c) + 1; // WRONG β left can jump back
left = Math.max(left, lastSeen.get(c) + 1); // CORRECT β only move forwardMap<Integer, Boolean> seen = new HashMap<>(); // wasteful
Set<Integer> seen = new HashSet<>(); // correct for existence-only checksint val = map.get(key); // NPE if key absent β WRONG
int val = map.getOrDefault(key, 0); // safe β CORRECT| Problem | Time | Space | Notes |
|---|---|---|---|
| Two Sum | O(n) | O(n) | single pass |
| Valid Anagram | O(n) | O(1) | at most 26 chars |
| First Non-Repeating | O(n) | O(1) | two passes, alphabet bounded |
| Max Balloons | O(n) | O(1) | fixed 5-letter check |
| Longest Palindrome | O(n) | O(1) | at most 52 chars (a-z + A-Z) |
| Ransom Note | O(n + m) | O(1) | n = note length, m = magazine length |
| Subarray Sum = K | O(n) | O(n) | prefix sum map |
| Longest Substring No Repeat | O(n) | O(min(n,Ξ±)) | Ξ± = alphabet size |
| Group Anagrams | O(n Β· k log k) | O(n Β· k) | k = avg string length |
| Top K Frequent (heap) | O(n log k) | O(n) | better than O(n log n) sort |
| Jewels and Stones | O(j + s) | O(j) | j = jewels length, s = stones length |
| Isomorphic Strings | O(n) | O(1) | at most 256 chars |
| Word Pattern | O(n) | O(n) | n = number of words |
| Contains Duplicate II | O(n) | O(min(n,k)) | sliding window of size k |
| Longest Consecutive Sequence | O(n) | O(n) | each element processed at most twice |
| LRU Cache | O(1) per op | O(capacity) | HashMap + DLL |
General rule:
- HashMap gives O(n) time at the cost of O(n) extra space
- Space is often O(1) or O(alphabet size) when keys are characters
- When ordering matters β use
LinkedHashMap(insertion order) orTreeMap(sorted)
HashMap pattern covers roughly 25β30% of array and string problems in coding interviews.
Master this pattern before moving to sliding window, two pointers, or binary search.