-
Notifications
You must be signed in to change notification settings - Fork 273
Sorting the Mail Room
TIP103 Unit 11 Session 1 (Click for link to problem statements)
The mail room receives a pile of labels and wants to bundle together any labels that are rearrangements of the same letters. Given a list of strings words, group the ones that are anagrams of each other.
Return a list of groups; the order of the groups and the order within each group do not matter.
def group_anagrams(words):
pass- 💡 Difficulty: Medium
- ⏰ Time to complete: 20-25 mins
- 🛠️ Topics: Strings, Hash Maps, Anagrams, Sorting
Understand what the interviewer is asking for by using test cases and questions about the problem.
- Established a set (2-3) of test cases to verify their own solution later.
- Established a set (1-2) of edge cases to verify their solution handles complexities.
- Have fully understood the problem and have no clarifying questions.
- Have you verified any Time/Space Constraints for this problem?
-
Q: What makes two labels belong in the same bundle?
- A: They are anagrams of each other, meaning they contain exactly the same letters with the same counts, just in a different order (e.g.,
"eat"and"tea").
- A: They are anagrams of each other, meaning they contain exactly the same letters with the same counts, just in a different order (e.g.,
-
Q: Does the order of the groups, or the order of words within a group, matter?
- A: No. Any ordering of the groups and any ordering within each group is accepted.
-
Q: Can the same word appear more than once in
words?- A: Yes. Duplicate words are anagrams of each other, so all copies belong in the same group.
HAPPY CASE
Input: words = ["eat", "tea", "tan", "ate", "nat", "bat"]
Output: [['eat', 'tea', 'ate'], ['tan', 'nat'], ['bat']]
Explanation: "eat", "tea", and "ate" all use the letters a, e, t. "tan" and "nat" use a, n, t. "bat" shares its letters with no other label, so it forms a group by itself.
EDGE CASE
Input: words = []
Output: []
Explanation: With no labels to bundle, there are no groups.
Input: words = ["bat"]
Output: [['bat']]
Explanation: A single label forms its own group of one.
Match what this problem looks like to known categories of problems, e.g. Linked List or Dynamic Programming, and strategies or patterns in those categories.
For Grouping / Categorization Problems, we can consider the following approaches:
-
Hash Map with a Canonical Key: Map each word to a signature that is identical for all of its anagrams, and collect words that share a signature. Sorting a word's letters produces such a signature (
"eat","tea", and"ate"all sort to"aet"). - Frequency-Count Key: Alternatively, a tuple of 26 letter counts can serve as the signature, avoiding the sort at the cost of a bulkier key.
Plan the solution with appropriate visualizations and pseudocode.
General Idea:
Two words are anagrams exactly when their letters, sorted alphabetically, are identical. Build a dictionary that maps each sorted-letter signature to the list of words that produce it. Every word in words gets sorted, looked up, and appended to its bucket. The dictionary's values are the anagram groups.
1) Create an empty dictionary `groups` mapping signature -> list of words.
2) For each `word` in `words`:
a) Compute the signature by sorting the letters of `word` and joining them into a string.
b) If the signature is not yet a key in `groups`, add it with an empty list.
c) Append `word` to the list at that signature.
3) Return the lists stored in `groups` as the final answer.
- Using
sorted(word)(a list) directly as a dictionary key — lists are unhashable, so join it into a string or convert it to a tuple first. - Comparing every pair of words with a nested loop, which turns an
O(N)pass intoO(N^2)anagram checks. - Returning the dictionary itself instead of its values — the problem asks for a list of groups.
Implement the code to solve the algorithm.
def group_anagrams(words):
groups = {}
for word in words:
# Words that are anagrams share the same sorted letters
key = "".join(sorted(word))
if key not in groups:
groups[key] = []
groups[key].append(word)
return list(groups.values())Review the code by running specific example(s) and recording values (watchlist) of your code's variables along the way.
-
Input: words = ["eat", "tea", "tan", "ate", "nat", "bat"]
- "eat" → key "aet", groups = {"aet": ["eat"]}
- "tea" → key "aet", groups = {"aet": ["eat", "tea"]}
- "tan" → key "ant", groups = {"aet": ["eat", "tea"], "ant": ["tan"]}
- "ate" → key "aet", groups = {"aet": ["eat", "tea", "ate"], "ant": ["tan"]}
- "nat" → key "ant", groups = {"aet": ["eat", "tea", "ate"], "ant": ["tan", "nat"]}
- "bat" → key "abt", groups = {"aet": ["eat", "tea", "ate"], "ant": ["tan", "nat"], "abt": ["bat"]}
- Output: 'eat', 'tea', 'ate'], ['tan', 'nat'], ['bat'
-
Input: words = []
- The loop never runs, so
groupsstays empty. - Output: []
- The loop never runs, so
Evaluate the performance of your algorithm and state any strong/weak or future potential work.
Assume N is the number of words and K is the length of the longest word.
-
Time Complexity:
O(N * K log K)because each of theNwords is sorted inO(K log K)time to build its signature; dictionary insertions and lookups areO(K)on average. -
Space Complexity:
O(N * K)to store every word (and its signature key) in the dictionary.
Using a 26-letter frequency-count tuple as the key would drop the per-word cost to O(K), giving O(N * K) overall time at the expense of slightly more bookkeeping.