Skip to content

Commit ef59ec1

Browse files
committed
feat(strings, anagrams): group anagrams
1 parent e84daec commit ef59ec1

File tree

5 files changed

+141
-0
lines changed

5 files changed

+141
-0
lines changed
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Group Anagrams
2+
3+
Given a list of words or phrases, group the words that are anagrams of each other. An anagram is a word or phrase formed
4+
from another word by rearranging its letters.
5+
6+
Constraints:
7+
8+
Let `strs` be the list of strings given as input to find the anagrams.
9+
10+
- 1 <= `strs.length` <=10^3
11+
- 0 <= `strs[i].length` <= 100
12+
- `strs[i]` consists of lowercase English letters
13+
14+
> Note the order in which the output is displayed doesn't matter
15+
16+
## Examples
17+
18+
![Example one](images/group_anagrams_example_one.png)
19+
![Example two](images/group_anagrams_example_two.png)
20+
21+
22+
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
from typing import List, Dict, Tuple
2+
from collections import defaultdict
3+
4+
5+
def group_anagrams_naive(strs: List[str]) -> List[List[str]]:
6+
"""
7+
Groups a list of strings by their anagrams.
8+
9+
Parameters:
10+
strs (List[str]): A list of strings
11+
12+
Returns:
13+
List[List[str]]: A list of lists, where each inner list contains strings that are anagrams of each other
14+
"""
15+
word_map: Dict[str, List[str]] = defaultdict(list)
16+
# traversing the list of strings takes O(n) time where n is the number of strings in this list
17+
for word in strs:
18+
# Note that the sorting here takes O(nlog(n)) time were n is the number of characters in this string
19+
key = "".join(sorted(word.lower()))
20+
word_map[key].append(word)
21+
22+
return list(word_map.values())
23+
24+
def group_anagrams(strs: List[str]) -> List[List[str]]:
25+
"""
26+
Groups a list of strings by their anagrams.
27+
28+
This uses A better approach than sorting can be used to solve this problem. This solution involves computing the
29+
frequency of each letter in every string. This will help reduce the time complexity of the given problem. We’ll just
30+
compute the frequency of every string and store the strings in their respective list in a hash map.
31+
32+
We see that all members of each set are characterized by the same frequency of each letter. This means that the
33+
frequency of each letter in the words belonging to the same group is equal. In the set [["speed", "spede"]], the
34+
frequency of the characters s, p, e, and d are the same in each word.
35+
36+
Let’s see how we can implement the above algorithm:
37+
38+
- For each string, compute a 6-element list. Each element in this list represents the frequency of an English letter
39+
in the corresponding string. This frequency count will be represented as a tuple. For example, "abbccc" will be
40+
represented as (1, 2, 3, 0, 0, ..., 0). This mapping will generate identical lists for strings that are anagrams.
41+
42+
- Use this list as a key to insert the strings into a hash map. All anagrams will be mapped to the same key in this
43+
hash map.
44+
45+
- While traversing each string, we generate its 26-element list and check if this list is present as a key in the
46+
hash map. If it does, we'll append the string to the array corresponding to that key. Otherwise, we'll add the new
47+
key-value pair to the hash map.
48+
49+
- Return the values of the hash map in a two-dimensional array, since each value will be an individual set of
50+
anagrams.
51+
52+
Parameters:
53+
strs (List[str]): A list of strings
54+
55+
Returns:
56+
List[List[str]]: A list of lists, where each inner list contains strings that are anagrams of each other
57+
"""
58+
word_map: Dict[Tuple[int,...], List[str]] = defaultdict(list)
59+
# traversing the list of strings takes O(n) time where n is the number of strings in this list
60+
for word in strs:
61+
count = [0] * 26
62+
for char in word:
63+
index = ord(char) - ord('a')
64+
count[index] += 1
65+
66+
key = tuple(count)
67+
word_map[key].append(word)
68+
69+
return list(word_map.values())
25.2 KB
Loading
21.5 KB
Loading
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
import unittest
2+
from . import group_anagrams
3+
4+
5+
class GroupAnagramsTestCase(unittest.TestCase):
6+
def test_1(self):
7+
strs = ["eat", "beat", "neat", "tea"]
8+
expected = [["eat", "tea"], ["beat"], ["neat"]]
9+
actual = group_anagrams(strs)
10+
self.assertEqual(expected, actual)
11+
12+
def test_2(self):
13+
strs = ["duel", "dule", "speed", "spede", "deul", "cars"]
14+
expected = [["duel", "dule", "deul"], ["speed", "spede"], ["cars"]]
15+
actual = group_anagrams(strs)
16+
self.assertEqual(expected, actual)
17+
18+
def test_3(self):
19+
strs = ["eat","tea","tan","ate","nat","bat"]
20+
expected = [["eat","tea","ate"],["tan","nat"],["bat"]]
21+
actual = group_anagrams(strs)
22+
self.assertEqual(expected, actual)
23+
24+
def test_4(self):
25+
strs = ["word","sword","drow","rowd","iced","dice"]
26+
expected = [["word","drow","rowd"],["sword"],["iced","dice"]]
27+
actual = group_anagrams(strs)
28+
self.assertEqual(expected, actual)
29+
30+
def test_5(self):
31+
strs = ["eat","drink","sleep","repeat"]
32+
expected = [["eat"],["drink"],["sleep"],["repeat"]]
33+
actual = group_anagrams(strs)
34+
self.assertEqual(expected, actual)
35+
36+
def test_6(self):
37+
strs = ["hello","ohlle","dark"]
38+
expected = [["hello","ohlle"],["dark"]]
39+
actual = group_anagrams(strs)
40+
self.assertEqual(expected, actual)
41+
42+
def test_7(self):
43+
strs = ["eat","beat","neat","tea"]
44+
expected = [["eat","tea"],["beat"],["neat"]]
45+
actual = group_anagrams(strs)
46+
self.assertEqual(expected, actual)
47+
48+
49+
if __name__ == '__main__':
50+
unittest.main()

0 commit comments

Comments
 (0)