Back to Arrays And Hashing
Group Anagrams
Group Anagrams
š§© Problem Statement
Given an array of strings strs, group the anagrams together. You can return the answer in any order.
An Anagram is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once.
š¹ Example 1:
Input:
strs = ["eat","tea","tan","ate","nat","bat"]
Output:
[["bat"],["nat","tan"],["ate","eat","tea"]]
š¹ Example 2:
Input:
strs = [""]
Output:
[[""]]
š Approaches
1. Categorize by Count (Hash Map)
Two strings are anagrams if and only if their character counts (how many 'a's, 'b's, etc.) are exactly the same.
⨠Intuition
- We can transform each string into a "key" that represents its character count.
eat->1 'a', 1 'e', 1 't'-> key(1, 0, 0, ..., 1, ..., 1)(tuple of 26 counts).tea-> key(1, 0, 0, ..., 1, ..., 1).- All anagrams will map to the same key.
- We group strings in a hash map where
key->list of strings.
š„ Algorithm Steps
- Initialize
ansas a default dictionary (hash map) mapping values to lists. - Iterate through each string
sinstrs. - Create a count array of size 26 (initialized to 0) for each string.
- Count the frequency of each char in
sand update the array. - Convert the array to a tuple (immutable) to use as a key.
- Append
sto the list atans[key]. - Return
ans.values().
ā³ Time & Space Complexity
- Time Complexity: $O(m \cdot n)$, where $m$ is the number of strings and $n$ is the average length of a string. Counting takes $O(n)$ for each string.
- Space Complexity: $O(m \cdot n)$, to store the strings in the hash map.
š Code Implementations
C++
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<string, vector<string>> m;
for (string s : strs) {
string t = s;
sort(t.begin(), t.end()); // Using sorted string as key
m[t].push_back(s);
}
vector<vector<string>> result;
for (auto p : m) {
result.push_back(p.second);
}
return result;
}
};
Note: The C++ solution above uses sorting as the key ($O(m \cdot n \log n)$) which is easier to implement with
std::string.
Python
from collections import defaultdict
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
ans = defaultdict(list)
for s in strs:
count = [0] * 26
for c in s:
count[ord(c) - ord("a")] += 1
ans[tuple(count)].append(s)
return list(ans.values())
Java
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[] ca = s.toCharArray();
Arrays.sort(ca);
String keyStr = String.valueOf(ca);
if (!map.containsKey(keyStr)) map.put(keyStr, new ArrayList<>());
map.get(keyStr).add(s);
}
return new ArrayList<>(map.values());
}
}
š Real-World Analogy
Sorting Laundry:
Imagine you have a pile of socks (strings).
- You want to group them by type (anagrams).
- You pick up a sock, identify its "signature" (color, pattern, size) -> this is grouping by "character count".
- You throw all matching socks into the same basket (list).
šÆ Summary
ā
Canonical Form: Anagrams share a canonical form (sorted string or char count).
ā
Hash Map: Efficiently groups items by key.
Solution Code
O(n) TimeO(1) Space
#include <algorithm>
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>
using namespace std;
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string> &strs) {
unordered_map<string, vector<string>> m;
for (string s : strs) {
string t = s;
sort(t.begin(), t.end());
m[t].push_back(s);
}
vector<vector<string>> result;
for (auto p : m) {
result.push_back(p.second);
}
return result;
}
};
int main() {
Solution sol;
vector<string> strs = {"eat", "tea", "tan", "ate", "nat", "bat"};
vector<vector<string>> result = sol.groupAnagrams(strs);
for (const auto &group : result) {
cout << "[";
for (int i = 0; i < group.size(); ++i) {
cout << group[i] << (i < group.size() - 1 ? ", " : "");
}
cout << "] ";
}
cout << endl;
return 0;
}
SYSTEM STABLEUTF-8 ⢠STATIC_RENDER