81. Group strings that are anagrams.
Using Python 3.14, implement def group_anagrams(strs: list[str]) -> list[list[str]]. The input has 1 to 10,000 strings; each string has length 0 to 100 and contains only lowercase English letters. Group strings with identical character multisets, preserving duplicate input strings. Group order and order inside each group are unrestricted, but no string may be dropped or added. Return new lists, do not mutate strs, and use only the standard library. Inputs outside the contract need not be handled. Example: for ["eat","tea","tan","ate","nat","bat"], one valid output is [["eat","tea","ate"],["tan","nat"],["bat"]].
I would use a dictionary with a 26-number frequency tuple as the key. For each string, I count how many times each lowercase letter appears, convert the count list to a tuple, and append the original string to that key's group. Anagrams produce the same frequency tuple, so they go into the same list. Finally, I return all dictionary groups. The expected time is O(n * k), and the space complexity shown by this solution is O(n * k).
See the Code while reading this explanation.
We receive a list of lowercase English strings and need to put strings with exactly the same letters and letter counts into the same group. Every input occurrence must appear in the result, so duplicate strings must stay. We return new lists and do not change the input list. The order of the groups and the order inside each group do not matter. I use the count of each letter from a through z as a signature because anagrams always have the same counts.
- Can I rely on every string containing only lowercase English letters? Yes, that is guaranteed by the problem.
- Does the order of the groups or the strings inside each group matter? No, either order is accepted.
- Should duplicate input strings be preserved? Yes, every occurrence must remain in the output.
The function receives strs: list[str]. There can be 1 to 10,000 strings. Each string has length 0 to 100 and contains only lowercase English letters. We return a new list[list[str]]. Strings with identical character counts belong in the same group. We must preserve duplicate strings, and we must not mutate strs.
For each string, I create a list of 26 zeros. Position 0 represents a, position 1 represents b, and so on through position 25 for z. I count every character in the string. After counting, I convert the list to a tuple. A tuple is hashable, so it can be used as a dictionary key.
The dictionary maps frequency tuple -> list of original strings. If two strings have the same 26 counts, they produce the same key and are appended to the same list. The central invariant is that all strings stored under one key have exactly the same character counts.
The input is ["eat", "tea", "tan", "ate", "nat", "bat"].
For "eat", the counts for a, e, and t are each 1. Its signature becomes key K1, so the dictionary starts K1 with ["eat"].
For "tea", the same three letters occur once, so it produces K1 and joins the same group. K1 becomes ["eat", "tea"].
For "tan", the letters a, n, and t each occur once. That produces a different signature K2, so K2 starts as ["tan"].
For "ate", the counts match K1, so K1 becomes ["eat", "tea", "ate"].
For "nat", the counts match K2, so K2 becomes ["tan", "nat"].
For "bat", the letters a, b, and t each occur once. That creates a third signature K3 with ["bat"].
The dictionary therefore contains K1 -> ["eat", "tea", "ate"], K2 -> ["tan", "nat"], and K3 -> ["bat"]. One valid returned result is [["eat", "tea", "ate"], ["tan", "nat"], ["bat"]].
Two lowercase strings are anagrams exactly when every letter appears the same number of times in both strings. The 26-number tuple records those counts. Therefore, anagrams create identical keys and enter the same group. Strings with different character counts create different keys. Every input string is appended exactly once, so no string is dropped or added and duplicates are preserved.
I use defaultdict(list) so a new signature automatically starts with an empty list. For every character, ord(ch) - ord('a') converts the letter to an index from 0 through 25. After the whole string is counted, tuple(count) creates the dictionary key and groups[key].append(s) stores the unchanged original string. Finally, list(groups.values()) returns the groups as a new outer list.
Let n be the number of strings and k be the average string length. Counting all characters takes O(n * k) work. Python dictionary lookup and insertion are O(1) on average, so the expected total time is O(n * k). The diagram gives O(n * k) space for storing the frequency keys and grouped strings. Empty strings, repeated characters, and duplicate strings all work with the same logic.
The key insight is that anagrams have identical character frequencies. For each string, build a canonical signature containing 26 counts, one for each lowercase English letter. Convert that count list to a tuple and use it as a dictionary key. The dictionary maps each signature to the original strings with that signature. The invariant is that every string stored under the same dictionary key has exactly the same 26 character counts. Because equal character counts mean the strings are anagrams, each dictionary value becomes one correct anagram group.
from collections import defaultdict
def group_anagrams(strs: list[str]) -> list[list[str]]:
# Map each 26-letter frequency signature to its original strings.
groups = defaultdict(list)
for s in strs:
# Start a fresh frequency table for lowercase letters a through z.
count = [0] * 26
# Count every character in the current string at its a-z position.
for ch in s:
count[ord(ch) - ord("a")] += 1
# Convert the mutable count list to a hashable dictionary key.
key = tuple(count)
# Append every original occurrence so duplicate strings are preserved.
groups[key].append(s)
# Return new lists containing all groups. Their order is unrestricted.
return list(groups.values())
# Run the same example shown in the diagram.
example = ["eat", "tea", "tan", "ate", "nat", "bat"]
result = group_anagrams(example)
# One valid grouping is [["eat", "tea", "ate"], ["tan", "nat"], ["bat"]].
print(result)Let n be the number of strings and k be the average length of a string. We examine each character once while building its 26-number frequency signature, so the expected total time is O(n * k). Dictionary lookup and insertion are O(1) on average. The diagram shows O(n * k) space for the frequency keys and grouped strings. The returned groups also preserve every input occurrence, including duplicates.
This grouping pattern is useful when many items can be placed into buckets by a canonical signature. Examples include grouping words by character composition or grouping records that are considered equivalent when they produce the same fixed set of counts. It avoids comparing every item directly with every other item.
This problem checks whether you can recognize a grouping problem and build a canonical key for equivalent values. It tests correct dictionary use, the difference between mutable and hashable objects, duplicate preservation, and careful handling of the input contract. It also shows whether you can explain why character counts identify anagrams, write clear Python, and reason accurately about expected hash-map performance, time complexity, and memory use.
One mistake is reusing the same 26-element count list for multiple strings, which mixes their frequencies. Another is trying to use the mutable count list directly as a dictionary key instead of converting it to a tuple. Using a set for each group is also wrong because it would remove duplicate input strings. Another mistake is changing the original strings or input list even though the function must not mutate strs. Finally, do not claim that the displayed output order is the only valid order.
Explain the invariant before coding: two strings go to the same dictionary group exactly when their 26 lowercase-letter counts are identical. Then make the code follow that statement directly: count letters, convert the counts to a tuple, and append the original string.






