Group Anagrams
MedPrompt
Given an array of strings strs, group the anagrams together. You can return the answer in any order.
Example 1:
Input: strs = ["eat","tea","tan","ate","nat","bat"]
Output: [["bat"],["nat","tan"],["ate","eat","tea"]]
Explanation:
- There is no string in strs that can be rearranged to form
"bat". - The strings
"nat"and"tan"are anagrams as they can be rearranged to form each other. - The strings
"ate","eat", and"tea"are anagrams as they can be rearranged to form each other.
Example 2:
Input: strs = [""]
Output: [[""]]
Example 3:
Input: strs = ["a"]
Output: [["a"]]
Constraints:
1 <= strs.length <= 1040 <= strs[i].length <= 100strs[i]consists of lowercase English letters.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach is an optimization over the sorting method. Instead of sorting, we create a canonical key for each string based on its character counts. Two strings are anagrams if and only if they have the same character counts.
Algorithm
- Initialize a hash map
mapwherekeyis aStringandvalueis aList<String>. - Iterate through each string
sin the input arraystrs. - Create an integer array
countof size 26, initialized to zeros. - For each character
cins, incrementcount[c - 'a']. - Build a key string from the
countarray. A simple way is to append each count value preceded by a delimiter (e.g.,#) to aStringBuilder. - Use this generated
keyto store the original stringsin the map, creating a new list if the key is new. - After iterating through all strings, return a new list containing the values of the map.
Walkthrough
We use a hash map, similar to the previous approach. However, the key is generated differently. For each string, we compute its character frequency. Since the strings only contain lowercase English letters, we can use an integer array of size 26 for this. After counting, we need to convert this frequency array into a unique, hashable key. A common way is to create a string representation. For example, an array [1, 2, 0, ...] (one 'a', two 'b's) can be converted to a string like "1#2#0#...". We iterate through the input strs. For each string s, we calculate its character count array. We then build a key string from this count array. This key is used to store the original string s in the hash map. Finally, we return the lists of strings from the map's values. This method avoids the O(K log K) sorting cost, replacing it with a linear O(K) scan.
import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map; class Solution { public List<List<String>> groupAnagrams(String[] strs) { if (strs == null || strs.length == 0) { return new ArrayList<>(); } Map<String, List<String>> map = new HashMap<>(); for (String s : strs) { int[] count = new int[26]; for (char c : s.toCharArray()) { count[c - 'a']++; } StringBuilder sb = new StringBuilder(); for (int i = 0; i < 26; i++) { sb.append('#'); sb.append(count[i]); } String key = sb.toString(); if (!map.containsKey(key)) { map.put(key, new ArrayList<>()); } map.get(key).add(s); } return new ArrayList<>(map.values()); }}Complexity
Time
O(N * K)
Space
O(N * K)
Trade-offs
Pros
Most efficient time complexity among the three approaches.
Avoids the log K factor associated with sorting.
Cons
The key generation is slightly more complex than simply sorting the string.
This approach involves comparing every string with every other string in the array. For each pair, we check if they are anagrams. We use an auxiliary boolean array to keep track of strings that have already been grouped to avoid redundant checks and processing.
Algorithm
- Initialize an empty list of lists
resultand a boolean arrayvisitedof sizen(length ofstrs). - Loop through each string
strs[i]fromi = 0ton-1. - If
strs[i]has not been visited (visited[i]is false):- Create a new list
currentGroupand addstrs[i]to it. - Mark
visited[i]as true. - Loop through the remaining strings
strs[j]fromj = i+1ton-1. - If
strs[j]has not been visited and is an anagram ofstrs[i]:- Add
strs[j]tocurrentGroup. - Mark
visited[j]as true.
- Add
- Add
currentGroupto theresult.
- Create a new list
- Return
result.
Walkthrough
We iterate through the input array strs. For each string strs[i], we start a new group. We then iterate through the rest of the array (j from i+1 to n-1). For each strs[j], we check if it's an anagram of strs[i]. To check for anagrams, we can sort both strings and see if the sorted versions are equal. A visited array is used to mark strings that have been placed into a group, so they are not considered again as the starting point of a new group. This method is straightforward but highly inefficient due to the nested loops and repeated anagram checks.
import java.util.ArrayList;import java.util.Arrays;import java.util.List; class Solution { public List<List<String>> groupAnagrams(String[] strs) { if (strs == null || strs.length == 0) { return new ArrayList<>(); } List<List<String>> result = new ArrayList<>(); boolean[] visited = new boolean[strs.length]; for (int i = 0; i < strs.length; i++) { if (!visited[i]) { List<String> currentGroup = new ArrayList<>(); currentGroup.add(strs[i]); visited[i] = true; for (int j = i + 1; j < strs.length; j++) { if (!visited[j] && isAnagram(strs[i], strs[j])) { currentGroup.add(strs[j]); visited[j] = true; } } result.add(currentGroup); } } return result; } private boolean isAnagram(String s1, String s2) { if (s1.length() != s2.length()) { return false; } char[] arr1 = s1.toCharArray(); char[] arr2 = s2.toCharArray(); Arrays.sort(arr1); Arrays.sort(arr2); return Arrays.equals(arr1, arr2); }}Complexity
Time
O(N^2 * K log K)
Space
O(N*K)
Trade-offs
Pros
Conceptually simple and easy to understand.
Cons
Very high time complexity, making it impractical for large inputs.
Will likely result in a Time Limit Exceeded (TLE) error on most online judges for the given constraints.
A more efficient approach is to realize that all anagrams of a string become identical when their characters are sorted. We can use this sorted string as a canonical representation (a key) to group anagrams together in a hash map.
Algorithm
- Initialize a hash map
mapwherekeyis aStringandvalueis aList<String>. - Iterate through each string
sin the input arraystrs. - Convert
sto a character arraycharArray. - Sort
charArray. - Create a new string
keyfrom the sortedcharArray. - Retrieve the list of strings for
keyfrom the map. If it doesn't exist, create a new list. - Add the original string
sto this list. - Put the
keyand the updated list back into the map. - After the loop, the map's values will contain the grouped anagrams.
- Return a new list constructed from the values of the map.
Walkthrough
We create a hash map where keys are sorted strings and values are lists of original strings that correspond to that sorted key. We iterate through the input array strs. For each string, we convert it to a character array, sort the array, and then convert it back to a string. This sorted string serves as the key. We then use this key to add the original string to the corresponding list in the hash map. If the key is not yet in the map, we first create a new empty list for it. After processing all strings, the values of the hash map are the required groups of anagrams. We collect these lists and return them.
import java.util.ArrayList;import java.util.Arrays;import java.util.HashMap;import java.util.List;import java.util.Map; class Solution { public List<List<String>> groupAnagrams(String[] strs) { if (strs == null || strs.length == 0) { return new ArrayList<>(); } Map<String, List<String>> map = new HashMap<>(); for (String s : strs) { char[] charArray = s.toCharArray(); Arrays.sort(charArray); String key = new String(charArray); if (!map.containsKey(key)) { map.put(key, new ArrayList<>()); } map.get(key).add(s); } return new ArrayList<>(map.values()); }}Complexity
Time
O(N * K log K)
Space
O(N * K)
Trade-offs
Pros
Significantly faster than the brute-force approach.
The logic is clean and relies on a standard data structure (hash map).
Cons
The time complexity is dominated by sorting each string, which can be slightly less optimal than character counting.
Solutions
Solution
class Solution {public List<List<String>> groupAnagrams(String[] strs) { Map<String, List<String>> d = new HashMap<>(); for (String s : strs) { char[] t = s.toCharArray(); Arrays.sort(t); String k = String.valueOf(t); d.computeIfAbsent(k, key->new ArrayList<>()).add(s); } return new ArrayList<>(d.values()); }}using System.Collections.Generic ; public class Comparer : IEqualityComparer < string > { public bool Equals ( string left , string right ) { if ( left . Length != right . Length ) return false ; var leftCount = new int [ 26 ]; foreach ( var ch in left ) { ++ leftCount [ ch - 'a' ]; } var rightCount = new int [ 26 ]; foreach ( var ch in right ) { var index = ch - 'a' ; if (++ rightCount [ index ] > leftCount [ index ]) return false ; } return true ; } public int GetHashCode ( string obj ) { var hashCode = 0 ; for ( int i = 0 ; i < obj . Length ; ++ i ) { hashCode ^= 1 << ( obj [ i ] - 'a' ); } return hashCode ; } } public class Solution { public IList < IList < string >> GroupAnagrams ( string [] strs ) { var dict = new Dictionary < string , List < string >>( new Comparer ()); foreach ( var str in strs ) { List < string > list ; if (! dict . TryGetValue ( str , out list )) { list = new List < string >(); dict . Add ( str , list ); } list . Add ( str ); } foreach ( var list in dict . Values ) { list . Sort (); } return new List < IList < string >>( dict . Values ); } }class Solution {public: vector<vector<string>> groupAnagrams(vector<string> &strs) { unordered_map<string, vector<string>> d; for (auto &s : strs) { string k = s; sort(k.begin(), k.end()); d[k].emplace_back(s); } vector<vector<string>> ans; for (auto &[_, v] : d) ans.emplace_back(v); return ans; }};''' >>> from collections import defaultdict >>> d = defaultdict(list) >>> d["a"]=1 >>> d["b"]=2 >>> d defaultdict(<class 'list'>, {'a': 1, 'b': 2}) >>> d.values() dict_values([1, 2]) >>> list(d.values()) [1, 2] ### sorted(str) will return a list of chars >>> a = "sdfddxyz" >>> sorted(a) ['d', 'd', 'd', 'f', 's', 'x', 'y', 'z'] >>> "".join(sorted(a)) 'dddfsxyz' ''' class Solution : def groupAnagrams ( self , strs : List [ str ]) -> List [ List [ str ]]: d = defaultdict ( list ) for s in strs : k = "" . join ( sorted ( s )) d [ k ]. append ( s ) return list ( d . values ())Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.