Group words that are anagrams of each other.
groupAnagrams([]string{"eat", "tea", "tan", "ate", "nat", "bat"})// [[eat tea ate] [tan nat] [bat]]The obvious way is quadratic
Compare every word against every group’s representative using valid anagram. That is , and the comparison is doing the same work over and over.
Give each group a name
Two words are anagrams exactly when they have the same letter counts. So compute those counts once per word and use them as a key. Words with equal keys are anagrams, by definition, and a map does the grouping.
eat → counts → aet
No bucket had this signature, so eat opens a new one. The signature is the letter counts — order thrown away deliberately.
func groupAnagrams(strs []string) [][]string { var groups = make(map[[26]int][]string) var result = make([][]string, 0)
for _, str := range strs { var key = [26]int{}
for _, char := range []byte(str) { key[char-'a']++ }
groups[key] = append(groups[key], str) }
for _, val := range groups { result = append(result, val) }
return result}— each word read once — against for the sort-the-word version.
The other canonical key
Sorting each word produces a canonical form too:
r := []rune(str)slices.Sort(r)key := string(r)per word instead of , and no alphabet assumption. That last part matters: the count key needs a known, small alphabet, and breaks the moment uppercase, spaces or Unicode appear.
Use counts when the alphabet is fixed and small. Use sorting when it is not.
Iteration order
result is built by ranging over a map, and Go randomises map iteration
order deliberately — it is not insertion order, and it differs between runs of
the same binary.
That is fine here because the problem accepts any order. It would not be fine if the output were compared directly against a fixed expected value, which is a common way for tests around map-based code to become flaky.