LeetCode #49 Medium

Group Anagrams

Group Anagrams is LeetCode 49 (Medium). Given an array of strings strs, group anagrams together and return the groups in any order. Two words are anagrams when one is a rearrangement of the other's letters, like eat, tea and ate.

  • Up to 10⁴ words, each up to 100 lowercase English letters (possibly empty).
  • Comparing every pair of words is O(n²) comparisons; the target is one pass that gives each word a key.
Constraints
  • 1 <= strs.length <= 10⁴
  • 0 <= strs[i].length <= 100
  • strs[i] consists of lowercase English letters.
arrayhash-tablestringsorting
Open on LeetCode ↗
02

Intuition

Anagrams have the same letters in a different order, so the trick is a key that ignores order: every anagram of a word must produce the same key, and words that are not anagrams must not.

Counting letters gives exactly that. eat, tea and ate each have one a, one e and one t, so their 26 counts are equal, while tan differs. Use the counts as a hash map key and append each word to its key's list. The groups form on their own, without comparing any two words.

How to spot this pattern

Grouping items that are "the same up to order" means computing one standard key per item and grouping by it, instead of comparing pairs. The same idea solves Valid Anagram (compare two keys), Group Shifted Strings, and Find All Anagrams in a String (counts over a sliding window).

03

Approach

Try it first

Before reading on: what could you compute from eat alone that tea and ate would also produce, but tan would not? How would you use it to put every word in its group in one pass?

1

Map keys to lists

Create a hash map from key to list of words. Python's defaultdict(list) and Java's computeIfAbsent create the list the first time a key appears, so a new key needs no special case.

2

Count the letters of each word

For each word, fill 26 counters, one per letter: counts[ch - 'a'] += 1. That is O(k) for a word of length k. Two words are anagrams exactly when all 26 counts match.

3

Turn the counts into a key

A hash map key must not change, so a Python list will not do: use tuple(counts). C++ can use a 26-character string with one character per count; Java's Arrays.toString(counts) gives a string with separators.

4

Append each word and return

Append the word to the list under its key, then return all the lists. Each word is counted once and looked up once, so the whole run is O(n · k) for n words of length up to k.

04

Group Anagrams solution in Python | C++ | Java

▶1class Solution:
▶2 def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
▶3 groups = defaultdict(list)
▶4 for word in strs:
▶5 counts = [0] * 26
▶6 for ch in word:
▶7 counts[ord(ch) - ord("a")] += 1
▶8 groups[tuple(counts)].append(word)
▶9 return list(groups.values())
wordseatteatanatenatbatletter countsgroupsemptyno groups yet
strs[eat, tea, tan, ate, nat, bat]
groupsemptykey → list of words
Start. Each word gets a key computed from the word alone: how many times each of the 26 letters appears. Anagrams share every count, so they share a key.
wordseatteatanatenatbatletter countsa1e1t1other letters 0groupsemptycount the letters of eat
wordeat
keya1 e1 t1non-zero counts
Go through eat letter by letter and add 1 to that letter's counter. The order of the letters makes no difference to the counts, which is exactly why anagrams end up with the same key.
wordseatteatanatenatbatletter countsa1e1t1other letters 0groupsa1 e1 t1→eatnew key → new group
keya1 e1 t1
group[eat]
groups1
No earlier word had these counts, so eat starts a new group under this key.
wordseatteatanatenatbatletter countsa1e1t1other letters 0groupsa1 e1 t1→eatcount the letters of tea
wordtea
keya1 e1 t1non-zero counts
Count the letters of tea: a 1, e 1, t 1.
wordseatteatanatenatbatletter countsa1e1t1other letters 0groupsa1 e1 t1→eatteakey seen before → joins eat
keya1 e1 t1
group[eat, tea]
groups1
Same counts as eat. The hash map finds that key at once, and tea joins the group without being compared to any other word.
wordseatteatanatenatbatletter countsa1n1t1other letters 0groupsa1 e1 t1→eatteacount the letters of tan
wordtan
keya1 n1 t1non-zero counts
Count the letters of tan: a 1, n 1, t 1.
wordseatteatanatenatbatletter countsa1n1t1other letters 0groupsa1 e1 t1→eatteaa1 n1 t1→tannew key → new group
keya1 n1 t1
group[tan]
groups2
No earlier word had these counts, so tan starts a new group under this key.
wordseatteatanatenatbatletter countsa1e1t1other letters 0groupsa1 e1 t1→eatteaa1 n1 t1→tancount the letters of ate
wordate
keya1 e1 t1non-zero counts
Count the letters of ate: a 1, e 1, t 1.
wordseatteatanatenatbatletter countsa1e1t1other letters 0groupsa1 e1 t1→eatteaatea1 n1 t1→tankey seen before → joins eat, tea
keya1 e1 t1
group[eat, tea, ate]
groups2
Same counts as eat, tea. The hash map finds that key at once, and ate joins the group without being compared to any other word.
wordseatteatanatenatbatletter countsa1n1t1other letters 0groupsa1 e1 t1→eatteaatea1 n1 t1→tancount the letters of nat
wordnat
keya1 n1 t1non-zero counts
Count the letters of nat: a 1, n 1, t 1.
wordseatteatanatenatbatletter countsa1n1t1other letters 0groupsa1 e1 t1→eatteaatea1 n1 t1→tannatkey seen before → joins tan
keya1 n1 t1
group[tan, nat]
groups2
Same counts as tan. The hash map finds that key at once, and nat joins the group without being compared to any other word.
wordseatteatanatenatbatletter countsa1b1t1other letters 0groupsa1 e1 t1→eatteaatea1 n1 t1→tannatcount the letters of bat
wordbat
keya1 b1 t1non-zero counts
Count the letters of bat: a 1, b 1, t 1.
wordseatteatanatenatbatletter countsa1b1t1other letters 0groupsa1 e1 t1→eatteaatea1 n1 t1→tannata1 b1 t1→batnew key → new group
keya1 b1 t1
group[bat]
groups3
No earlier word had these counts, so bat starts a new group under this key.
wordseatteatanatenatbatletter countsgroupsa1 e1 t1→eatteaatea1 n1 t1→tannata1 b1 t1→batreturn 3 groups
answer[eat, tea, ate] [tan, nat] [bat]
Done. Each list in the map is one group of anagrams. Every word was counted once and looked up once, so the run is O(n · k). The problem accepts the groups in any order.
05

Sorted-letters key

Sorting a word's letters gives the same string for every anagram of it, so that sorted string is the key. The rest is identical: append each word under its key and return the lists.

▶1class Solution:
▶2 def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
▶3 groups = defaultdict(list)
▶4 for word in strs:
▶5 groups["".join(sorted(word))].append(word)
▶6 return list(groups.values())
06

Common pitfalls

Using a list as the dictionary key

✗ Wrong
groups[counts].append(word)
✓ Right
groups[tuple(counts)].append(word)

Lists can change, so Python refuses to hash them and raises TypeError: unhashable type: 'list'. A tuple holds the same counts and can be a key.

Sorting the list of words instead of each word

✗ Wrong
strs.sort()
# then look for neighbouring anagrams
✓ Right
for word in strs:
    groups[key(word)].append(word)

Sorting the list orders words alphabetically, which does not put anagrams next to each other: ate and eat end up far apart. It is the letters inside each word that need a fixed order.

Joining counts without a separator

✗ Wrong
key = ''.join(str(c) for c in counts)
✓ Right
key = tuple(counts)  # or '#'.join(map(str, counts))

Without a separator the numbers run together: counts 1, 11 and 11, 1 both become 111, so two different groups merge. Use a tuple, or join with a delimiter.

07

Edge cases

An empty string, e.g. [""]

All 26 counts are 0. That is a valid key, so the answer is [[""]].

Repeated words, e.g. ["ab", "ab"]

Both copies share a key and both stay in the group: [["ab", "ab"]]. Nothing is removed.

08

Complexity

Time
O(n · k)
Space
O(n · k)
n words of length up to k; counting builds each key in O(k). Every word is stored once in the map.
09

Group Anagrams FAQ

How is it written in Python?

Use defaultdict(list). For each word, build a key, either the tuple of its 26 letter counts or ''.join(sorted(word)), and append the word under it. Return list(groups.values()).