LC #49: Group Anagrams

๐ง Problem Statement
Given an array of strings, group the anagrams together. Return the grouped anagrams as a list of lists.
๐ Leetcode Link
๐ก Intuition
Anagrams share the same characters. If you sort their letters, they become identical. So, we can bucket words based on their sorted version.
๐ฅ Brute Force (Don't do this)
- Compare each string with every other using
isAnagram(s, t)
(a utility method inspired from Valid Anagram solution) - And store string accordingly in a list of lists
- Too slow for large inputs โ O(nยฒ * k)
โก Optimized Approach: Sorting as Key
- โ Sort the characters of each word
- โ Use the sorted string as a key in a HashMap
- โ Group words into the list mapped to that key
class Solution {
public List<List<String>> groupAnagrams(String[] strs) {
Map<String, List<String>> map = new HashMap<>();
for (String s : strs) {
char[] arr = s.toCharArray();
Arrays.sort(arr);
String key = new String(arr);
if (!map.containsKey(key)) {
map.put(key, new ArrayList<>());
}
map.get(key).add(s);
}
return new ArrayList<>(map.values());
}
}
- Time: O(n * k log k) โ n = num of words, k = avg word length
- Space: O(n * k) โ for storing map buckets
โก Even More Optimized (But Not As Readable) Approach
Instead of sorting each word (O(k log k)), we can use a faster trick:
- โ Count the frequency of each character using int[26]
- โ Convert this frequency array into a string key
- โ Group words in the map using that key
for (String s : strs) {
int[] count = new int[26];
for (char c : s.toCharArray()) {
count[c - 'a']++;
}
StringBuilder sb = new StringBuilder();
for (int freq : count) {
sb.append(freq).append('#');
}
String key = sb.toString();
if (!map.containsKey(key)) {
map.put(key, new ArrayList<>());
}
map.get(key).add(s);
}
โ ๏ธ Delimiter '#'
is important โ avoids merging counts like "1#10" vs "11"
โ Time: O(n * k) โ faster than sorting
โ Key becomes less readable (looks like a hash string), but grouping logic still works perfectly
๐งช Test Cases + Dry Run
โ Main Test Case 1
Input: ["eat", "tea", "ate"]
๐ Dry Run
Word | Sorted Key | Map Update |
eat | aet | {aet: [eat]} |
tea | aet | {aet: [eat, tea]} |
ate | aet | {aet: [eat, tea, ate]} |
โก๏ธ Output: [["eat", "tea", "ate"]]
โ Main Test Case 2
Input: ["bat", "tab", "tan", "nat"]
๐ Dry Run
Word | Sorted Key | Map Update |
bat | abt | {abt: [bat]} |
tab | abt | {abt: [bat, tab]} |
tan | ant | {abt: [bat, tab], ant: [tan]} |
nat | ant | {abt: [bat, tab], ant: [tan, nat]} |
โก๏ธ Output: [["bat", "tab"], ["tan", "nat"]]
โ ๏ธ Edge Case 1 โ Single Empty String
Input: [""]
๐ Dry Run
Word | Sorted Key | Map Update |
"" | "" | {"": [""]} |
โก๏ธ Output: [[""]]
โ ๏ธ Edge Case 2 โ Multiple Empty Strings
Input: ["", ""]
๐ Dry Run
Word | Sorted Key | Map Update |
"" | "" | {"": [""]} |
"" | "" | {"": ["", ""]} |
โก๏ธ Output: [["", ""]]
๐ง Key Insights & Gotchas
- โ
Initially thought of
isAnagram()
+ compare with keys โ O(nยฒ) โ Not optimal - โ Sorting each word works well โ O(k log k)
- โก Faster alt: use int[26] frequency count as key (less readable though)
- โ
new String(char[])
is key โ notarr.toString()
- โ
new ArrayList<>(map.values())
gives final output
โ๏ธ Similar Problems
Find All Anagrams in a String โ LC #438
โช Instead of grouping words, slide a window over a string and check if the current window is an anagram of a pattern (freq count matches)Valid Anagram โ LC #242
โช Basic version: check if two words are anagrams using sorting or char countsGroup Shifted Strings โ LC #249
โช Group words with the same shifting pattern (e.g., "abc" โ "bcd") using offset diff logic
๐ TL;DR
- Group words by their sorted form
- Map from sorted key โ original words
- Avoid extra comparisons โ sorting is the only grouping logic needed
Subscribe to my newsletter
Read articles from LilSardarX directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

LilSardarX
LilSardarX
Iโm figuring things out in tech โ and blogging along the way. I write what I learn, so I can revise it later and maybe help you avoid a few rabbit holes.