Hash-based patterns are the default when you need fast membership checks, counts, or complement lookups. This pattern often converts nested-loop logic into linear-time solutions.
Why This Pattern Matters
Use it when problems ask for:
- duplicates / uniqueness
- frequency counts
- pair complements
- grouping by key
HashSet gives near O(1) membership checks.
HashMap gives near O(1) key-value updates and lookups.
Template 1: Frequency Counter
public Map<Character, Integer> buildFrequency(String s) {
Map<Character, Integer> freq = new HashMap<>();
for (char c : s.toCharArray()) {
freq.put(c, freq.getOrDefault(c, 0) + 1);
}
return freq;
}
Template 2: Seen Set
public boolean containsDuplicate(int[] nums) {
Set<Integer> seen = new HashSet<>();
for (int x : nums) {
if (!seen.add(x)) return true;
}
return false;
}
Problem 1: Two Sum
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> index = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
int need = target - nums[i];
if (index.containsKey(need)) {
return new int[]{index.get(need), i};
}
index.put(nums[i], i);
}
return new int[]{-1, -1};
}
Time: O(n)
Space: O(n)
Problem 2: Valid Anagram
public boolean isAnagram(String s, String t) {
if (s.length() != t.length()) return false;
int[] count = new int[26];
for (int i = 0; i < s.length(); i++) {
count[s.charAt(i) - 'a']++;
count[t.charAt(i) - 'a']--;
}
for (int x : count) {
if (x != 0) return false;
}
return true;
}
Problem 3: Longest Consecutive Sequence
public int longestConsecutive(int[] nums) {
Set<Integer> set = new HashSet<>();
for (int x : nums) set.add(x);
int best = 0;
for (int x : set) {
if (!set.contains(x - 1)) { // start of sequence
int curr = x;
int len = 1;
while (set.contains(curr + 1)) {
curr++;
len++;
}
best = Math.max(best, len);
}
}
return best;
}
Problem 4: Group Anagrams
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> groups = new HashMap<>();
for (String s : strs) {
char[] arr = s.toCharArray();
Arrays.sort(arr);
String key = new String(arr);
groups.computeIfAbsent(key, k -> new ArrayList<>()).add(s);
}
return new ArrayList<>(groups.values());
}
API Tips: merge and computeIfAbsent
These APIs reduce boilerplate and edge bugs:
freq.merge(c, 1, Integer::sum);
groups.computeIfAbsent(key, k -> new ArrayList<>()).add(value);
Prefer them over repeated containsKey checks when possible.
Determinism Note for Tests
HashMap/HashSet iteration order is not guaranteed.
If test output comparison depends on order:
- sort inner/outer collections before assert, or
- use
LinkedHashMapwhen insertion order matters by design
This avoids flaky tests and hidden order coupling.
Common Mistakes
- Using mutable objects as map keys without stable
equals/hashCode - Forgetting collision-safe key design for grouping problems
- Assuming insertion order in
HashMap/HashSet - Not handling null/empty edge cases
Practice Set (Recommended Order)
- Contains Duplicate (LC 217)
LeetCode - Two Sum (LC 1)
LeetCode - Valid Anagram (LC 242)
LeetCode - Group Anagrams (LC 49)
LeetCode - Longest Consecutive Sequence (LC 128)
LeetCode - Top K Frequent Elements (LC 347)
LeetCode
Key Takeaways
- Hash patterns are first choice for lookup-heavy problems.
- Frequency maps and seen sets remove nested scans.
- Correct key design is the difference between right and wrong solutions.
Comments