Group Anagrams

MediumHash Tables · Canonical Key🧩 Hashing & CountingLeetCode #49 ↗Updated

Problem

You are given an array of lowercase strings strs. Partition them into groups such that two words share a group exactly when one is a rearrangement of the letters of the other. Return the groups; neither the group order nor the order inside a group matters.
Input["eat","tea","tan","ate","nat","bat"]Output[[bat],[nat,tan],[ate,eat,tea]]
Input[""]Output[[""]]
Input["a"]Output[["a"]]

1 ≤ n ≤ 104  ·  0 ≤ strs[i].length ≤ 100  ·  lowercase English letters only

Try it yourself

Java
class Solution {
    public List<List<String>> groupAnagrams(String[] strs) {
        
    }
}
Your code is saved in this browser.Submit on LeetCode ↗
Key insight
Give every word a canonical key that is identical for all its anagrams (sorted letters, or the 26 letter counts), then bucket words by key in a hash map.
PatternHashing & Counting

Instinct Need "have I seen this before?", "how many times?" or "group things that look alike"? Trade memory for O(1) lookups with a HashMap, HashSet or int[26].

Memory hack Don't search, remember - every element checks in at the front desk, and the desk answers instantly.

Open the Hashing & Counting guide →

Approaches

Brute ForcePairwise Anagram Check

Time O(n2 · k)Space O(n · k)

Take the first unassigned word as a group leader, then scan all later words and pull in every unassigned word that is an anagram of it (checked with a 26-count array in O(k)). A used[] flag stops a word joining twice.

Java
public List<List<String>> groupAnagrams(String[] strs) {
    List<List<String>> res = new ArrayList<>();
    boolean[] used = new boolean[strs.length];
    for (int i = 0; i < strs.length; i++) {
        if (used[i]) continue;
        List<String> group = new ArrayList<>();
        group.add(strs[i]);
        for (int j = i + 1; j < strs.length; j++) {
            if (!used[j] && isAnagram(strs[i], strs[j])) {
                group.add(strs[j]);
                used[j] = true;
            }
        }
        res.add(group);
    }
    return res;
}

private boolean isAnagram(String a, String b) {
    if (a.length() != b.length()) return false;
    int[] cnt = new int[26];
    for (int i = 0; i < a.length(); i++) {
        cnt[a.charAt(i) - 'a']++;
        cnt[b.charAt(i) - 'a']--;
    }
    for (int c : cnt) if (c != 0) return false;
    return true;
}

Correct and simple, but compares up to n2/2 word pairs.

At a glance

LevelApproachTimeSpace
Brute ForcePairwise Anagram CheckO(n2 · k)O(n · k)
BetterHash Map Keyed by Sorted WordO(n · k log k)O(n · k)
OptimalHash Map Keyed by Letter CountsO(n · k)O(n · k)
Common pitfalls
  • Building the count key without separators — different count vectors can collide.
  • Using int[] directly as a map key — arrays hash by identity, not content.
  • Dropping the empty string — "" is a valid word and forms its own group.
Interview tips
  • Frame it as “choose a canonical form”, then compare sort-key vs count-key costs.
  • Ask whether input is lowercase-only; for Unicode, prefer the sorted key.
  • Mention computeIfAbsent to keep the grouping code to one line.
Pattern
Canonical key + hash map grouping → bucket items that are equivalent under some transformation.
My notes saved in this browser