Put words with the same letters into the same group.
Start with the concrete trace below. It shows the state the algorithm must carry as it runs.
Problem trace
Group Anagrams: Use each word's 26-letter frequency tuple as its bucket address.
Input and goal Put words with the same letters into the same group.
Initialize the group map The map is empty and the fresh 26-count array for "eat" is all zeros.
eat[0] e 0 a 1 t 2 | 3 t 4 e 5 a 6 | 7 t 8 a 9 n 10
example words = ["eat", "tea", "tan"]mapLabel 26-count tuple -> bucketcounts a0 e0 t0; every other letter 0
26-count tuple -> bucket empty
Count e in eat The character e increments slot ord("e")-ord("a") from 0 to 1.
e: 0 -> 1 e 0 a 1 t 2 | 3 t 4 e 5 a 6 | 7 t 8 a 9 n 10
example words = ["eat", "tea", "tan"]mapLabel 26-count tuple -> bucketcounts a0 e1 t0; others 0
26-count tuple -> bucket empty
Count a in eat The character a increments its slot, preserving e:1.
e 0 a: 0 -> 1 a 1 t 2 | 3 t 4 e 5 a 6 | 7 t 8 a 9 n 10
example words = ["eat", "tea", "tan"]mapLabel 26-count tuple -> bucketcounts a1 e1 t0; others 0
26-count tuple -> bucket empty
Count t and append eat The completed tuple has a:1, e:1, t:1 and zeros elsewhere; append "eat" at that exact key.
e 0 a 1 t: 0 -> 1; append t 2 | 3 t 4 e 5 a 6 | 7 t 8 a 9 n 10
example words = ["eat", "tea", "tan"]mapLabel 26-count tuple -> bucketsignature (a1,e1,t1; others 0)
26-count tuple -> bucket (a1,e1,t1; others 0) [eat]
Reset and count t in tea A fresh all-zero count array is created for "tea"; its first character sets t:1.
e 0 a 1 t 2 | 3 new counts; t: 0 -> 1 t 4 e 5 a 6 | 7 t 8 a 9 n 10
example words = ["eat", "tea", "tan"]mapLabel 26-count tuple -> bucketcounts a0 e0 t1; others 0
26-count tuple -> bucket (a1,e1,t1; others 0) [eat]
Count e in tea The second character sets e:1 while t remains 1.
e 0 a 1 t 2 | 3 t 4 e: 0 -> 1 e 5 a 6 | 7 t 8 a 9 n 10
example words = ["eat", "tea", "tan"]mapLabel 26-count tuple -> bucketcounts a0 e1 t1; others 0
26-count tuple -> bucket (a1,e1,t1; others 0) [eat]
Count a and reuse the bucket After a becomes 1, tea has the same complete 26-count tuple as eat, so append to the existing bucket.
e 0 a 1 t 2 | 3 t 4 e 5 a: 0 -> 1; append a 6 | 7 t 8 a 9 n 10
example words = ["eat", "tea", "tan"]mapLabel 26-count tuple -> bucketsignature (a1,e1,t1; others 0)
26-count tuple -> bucket (a1,e1,t1; others 0) [eat, tea]
Reset and count t in tan A third fresh count array starts at zero; t becomes 1.
e 0 a 1 t 2 | 3 t 4 e 5 a 6 | 7 new counts; t: 0 -> 1 t 8 a 9 n 10
example words = ["eat", "tea", "tan"]mapLabel 26-count tuple -> bucketcounts a0 n0 t1; others 0
26-count tuple -> bucket (a1,e1,t1; others 0) [eat, tea]
Count a in tan The second character sets a:1 while t remains 1.
e 0 a 1 t 2 | 3 t 4 e 5 a 6 | 7 t 8 a: 0 -> 1 a 9 n 10
example words = ["eat", "tea", "tan"]mapLabel 26-count tuple -> bucketcounts a1 n0 t1; others 0
26-count tuple -> bucket (a1,e1,t1; others 0) [eat, tea]
Count n and create a bucket The completed tan tuple has a:1, n:1, t:1. It differs from the eat/tea key at e and n, so defaultdict creates a new bucket.
e 0 a 1 t 2 | 3 t 4 e 5 a 6 | 7 t 8 a 9 n: 0 -> 1; append n 10
example words = ["eat", "tea", "tan"]mapLabel 26-count tuple -> bucketsignature (a1,n1,t1; others 0)branch new key
26-count tuple -> bucket (a1,e1,t1; others 0) [eat, tea] (a1,n1,t1; others 0) [tan]
Return the bucket values Insertion order leaves two map values: [eat, tea] for the shared tuple and [tan] for the distinct tuple.
e 0 a 1 t 2 | 3 t 4 e 5 a 6 | 7 t 8 a 9 scan complete n 10
example words = ["eat", "tea", "tan"]mapLabel 26-count tuple -> bucketbuckets 2result [["eat", "tea"], ["tan"]]
26-count tuple -> bucket (a1,e1,t1; others 0) [eat, tea] (a1,n1,t1; others 0) [tan]
← Previous ▶ Play trace Next →
Step 1 of 11 1 Initialize the group map 2 Count e in eat 3 Count a in eat 4 Count t and append eat 5 Reset and count t in tea 6 Count e in tea 7 Count a and reuse the bucket 8 Reset and count t in tan 9 Count a in tan 10 Count n and create a bucket 11 Return the bucket values
Recognize it Many lowercase words must be partitioned by equal letter multiplicities while their original letter order is irrelevant.
Keep true After each word, every processed word appears once in the bucket keyed by its exact 26-count tuple; two words share a bucket exactly when they are anagrams.
Reuse it Canonicalize each item into an equality-preserving key, then group by that key; this transfers to shifted-string groups, normalized records, and equivalence-class indexing. Read it this way: The map is empty and the fresh 26-count array for "eat" is all zeros. Step through the frames to watch the state change. The last frame shows the answer or the stopping condition.
Pattern: Hash map with a shared key.
Simple idea: Count each lowercase letter. Anagrams have the same 26 counts, so they use
the same tuple as a map key.
from collections import defaultdict
def group_anagrams (words: list[ str ]) -> list[list[ str ]]:
groups: dict[tuple[ int , ... ], list[ str ]] = defaultdict( list )
for word in words:
counts = [ 0 ] * 26
for char in word:
counts[ ord (char) - ord ( "a" )] += 1
groups[ tuple (counts)].append(word)
return list (groups.values())
Cost: O ( nm ) time and O ( nm ) space for n words of average length m .