Skip to content

Commit 94c5dfd

Browse files
authored
feat: add 5 more problems (#54)
1 parent 10a73e3 commit 94c5dfd

38 files changed

+1570
-87
lines changed

Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
PYTHON_VERSION = 3.13
2-
PROBLEM ?= gas_station
2+
PROBLEM ?= design_add_and_search_words_data_structure
33
FORCE ?= 0
44
COMMA := ,
55

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Design Add and Search Words Data Structure
2+
3+
**Difficulty:** Medium
4+
**Topics:** String, Depth-First Search, Design, Trie
5+
**Tags:** grind
6+
7+
**LeetCode:** [Problem 211](https://leetcode.com/problems/design-add-and-search-words-data-structure/description/)
8+
9+
## Problem Description
10+
11+
Design a data structure that supports adding new words and finding if a string matches any previously added string.
12+
13+
Implement the `WordDictionary` class:
14+
15+
- `WordDictionary()` Initializes the object.
16+
- `void addWord(word)` Adds `word` to the data structure, it can be matched later.
17+
- `bool search(word)` Returns `true` if there is any string in the data structure that matches `word` or `false` otherwise. `word` may contain dots `'.'` where dots can be matched with any letter.
18+
19+
## Examples
20+
21+
### Example 1:
22+
23+
```
24+
Input
25+
["WordDictionary","addWord","addWord","addWord","search","search","search","search"]
26+
[[],["bad"],["dad"],["mad"],["pad"],["bad"],[".ad"],["b.."]]
27+
Output
28+
[null,null,null,null,false,true,true,true]
29+
30+
Explanation
31+
WordDictionary wordDictionary = new WordDictionary();
32+
wordDictionary.addWord("bad");
33+
wordDictionary.addWord("dad");
34+
wordDictionary.addWord("mad");
35+
wordDictionary.search("pad"); // return False
36+
wordDictionary.search("bad"); // return True
37+
wordDictionary.search(".ad"); // return True
38+
wordDictionary.search("b.."); // return True
39+
```
40+
41+
## Constraints
42+
43+
- `1 <= word.length <= 25`
44+
- `word` in `addWord` consists of lowercase English letters.
45+
- `word` in `search` consist of `'.'` or lowercase English letters.
46+
- There will be at most `2` dots in `word` for `search` queries.
47+
- At most `10^4` calls will be made to `addWord` and `search`.

leetcode/design_add_and_search_words_data_structure/__init__.py

Whitespace-only changes.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from typing import Any
2+
3+
4+
def run_word_dictionary(solution_class: type, operations: list[str], inputs: list[list[str]]):
5+
wd: Any = None
6+
results: list[bool | None] = []
7+
8+
for op, args in zip(operations, inputs):
9+
if op == "WordDictionary":
10+
wd = solution_class()
11+
results.append(None)
12+
elif op == "addWord":
13+
assert wd is not None
14+
wd.add_word(args[0])
15+
results.append(None)
16+
elif op == "search":
17+
assert wd is not None
18+
results.append(wd.search(args[0]))
19+
20+
return results
21+
22+
23+
def assert_word_dictionary(result: list[bool | None], expected: list[bool | None]) -> bool:
24+
assert result == expected
25+
return True
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# ---
2+
# jupyter:
3+
# jupytext:
4+
# text_representation:
5+
# extension: .py
6+
# format_name: percent
7+
# format_version: '1.3'
8+
# jupytext_version: 1.17.3
9+
# kernelspec:
10+
# display_name: leetcode-py-py3.13
11+
# language: python
12+
# name: python3
13+
# ---
14+
15+
# %%
16+
from helpers import assert_word_dictionary, run_word_dictionary
17+
from solution import WordDictionary
18+
19+
# %%
20+
# Example test case
21+
operations = ["WordDictionary", "addWord", "addWord", "addWord", "search", "search", "search", "search"]
22+
inputs = [[], ["bad"], ["dad"], ["mad"], ["pad"], ["bad"], [".ad"], ["b.."]]
23+
expected = [None, None, None, None, False, True, True, True]
24+
25+
# %%
26+
result = run_word_dictionary(WordDictionary, operations, inputs)
27+
result
28+
29+
# %%
30+
assert_word_dictionary(result, expected)
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
from typing import Any
2+
3+
4+
class WordDictionary:
5+
6+
# Time: O(1)
7+
# Space: O(1)
8+
def __init__(self) -> None:
9+
self.root: dict[str, Any] = {}
10+
11+
# Time: O(m) where m = len(word)
12+
# Space: O(m) for new word
13+
def add_word(self, word: str) -> None:
14+
node = self.root
15+
for char in word:
16+
if char not in node:
17+
node[char] = {}
18+
node = node[char]
19+
node["#"] = True
20+
21+
# Time: O(n * 26^k) where n = len(word), k = number of dots
22+
# Space: O(n) for recursion stack
23+
def search(self, word: str) -> bool:
24+
def dfs(i: int, node: dict[str, Any]) -> bool:
25+
if i == len(word):
26+
return "#" in node
27+
28+
char = word[i]
29+
if char == ".":
30+
for key in node:
31+
if key != "#" and dfs(i + 1, node[key]):
32+
return True
33+
return False
34+
else:
35+
return char in node and dfs(i + 1, node[char])
36+
37+
return dfs(0, self.root)
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
import pytest
2+
3+
from leetcode_py import logged_test
4+
5+
from .helpers import assert_word_dictionary, run_word_dictionary
6+
from .solution import WordDictionary
7+
8+
9+
class TestDesignAddAndSearchWordsDataStructure:
10+
11+
@logged_test
12+
@pytest.mark.parametrize(
13+
"operations, inputs, expected",
14+
[
15+
(
16+
[
17+
"WordDictionary",
18+
"addWord",
19+
"addWord",
20+
"addWord",
21+
"search",
22+
"search",
23+
"search",
24+
"search",
25+
],
26+
[[], ["bad"], ["dad"], ["mad"], ["pad"], ["bad"], [".ad"], ["b.."]],
27+
[None, None, None, None, False, True, True, True],
28+
),
29+
(
30+
["WordDictionary", "addWord", "search", "search", "search"],
31+
[[], ["a"], ["a"], ["."], ["aa"]],
32+
[None, None, True, True, False],
33+
),
34+
(
35+
["WordDictionary", "addWord", "addWord", "search", "search", "search"],
36+
[[], ["at"], ["and"], ["an"], [".at"], ["an."]],
37+
[None, None, None, False, False, True],
38+
),
39+
(
40+
["WordDictionary", "addWord", "addWord", "search", "search"],
41+
[[], ["word"], ["world"], ["word"], ["wor."]],
42+
[None, None, None, True, True],
43+
),
44+
(
45+
["WordDictionary", "addWord", "search", "search"],
46+
[[], ["test"], ["test"], ["t..t"]],
47+
[None, None, True, True],
48+
),
49+
(
50+
["WordDictionary", "addWord", "addWord", "search", "search", "search"],
51+
[[], ["a"], ["b"], ["a"], ["."], ["c"]],
52+
[None, None, None, True, True, False],
53+
),
54+
(
55+
["WordDictionary", "addWord", "addWord", "search", "search", "search"],
56+
[[], ["abc"], ["def"], ["..."], ["a.."], ["..f"]],
57+
[None, None, None, True, True, True],
58+
),
59+
(
60+
["WordDictionary", "addWord", "addWord", "search", "search", "search"],
61+
[
62+
[],
63+
["programming"],
64+
["algorithm"],
65+
["prog......."],
66+
["algo....."],
67+
["........ing"],
68+
],
69+
[None, None, None, True, True, True],
70+
),
71+
(
72+
["WordDictionary", "addWord", "addWord", "search", "search"],
73+
[[], ["x"], ["xy"], ["."], [".."]],
74+
[None, None, None, True, True],
75+
),
76+
(
77+
["WordDictionary", "addWord", "addWord", "search", "search", "search"],
78+
[[], ["hello"], ["world"], ["hi"], ["word"], ["......"]],
79+
[None, None, None, False, False, False],
80+
),
81+
(
82+
[
83+
"WordDictionary",
84+
"addWord",
85+
"addWord",
86+
"addWord",
87+
"search",
88+
"search",
89+
"search",
90+
"search",
91+
],
92+
[[], ["cat"], ["car"], ["card"], ["c.."], ["ca."], ["c..d"], ["....."]],
93+
[None, None, None, None, True, True, True, False],
94+
),
95+
(
96+
[
97+
"WordDictionary",
98+
"addWord",
99+
"addWord",
100+
"addWord",
101+
"search",
102+
"search",
103+
"search",
104+
],
105+
[
106+
[],
107+
["run"],
108+
["runner"],
109+
["running"],
110+
["run"],
111+
["run..."],
112+
["run....."],
113+
],
114+
[None, None, None, None, True, True, False],
115+
),
116+
(
117+
["WordDictionary", "addWord", "addWord", "search", "search", "search"],
118+
[[], ["abc"], ["xyz"], ["..."], [".."], ["...."]],
119+
[None, None, None, True, False, False],
120+
),
121+
],
122+
)
123+
def test_word_dictionary(
124+
self,
125+
operations: list[str],
126+
inputs: list[list[str]],
127+
expected: list[bool | None],
128+
):
129+
result = run_word_dictionary(WordDictionary, operations, inputs)
130+
assert_word_dictionary(result, expected)

leetcode/group_anagrams/README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
# Group Anagrams
2+
3+
**Difficulty:** Medium
4+
**Topics:** Array, Hash Table, String, Sorting
5+
**Tags:** grind
6+
7+
**LeetCode:** [Problem 49](https://leetcode.com/problems/group-anagrams/description/)
8+
9+
## Problem Description
10+
11+
Given an array of strings `strs`, group the anagrams together. You can return the answer in **any order**.
12+
13+
An **anagram** is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once.
14+
15+
## Examples
16+
17+
### Example 1:
18+
19+
```
20+
Input: strs = ["eat","tea","tan","ate","nat","bat"]
21+
Output: [["bat"],["nat","tan"],["ate","eat","tea"]]
22+
Explanation:
23+
- There is no string in strs that can be rearranged to form "bat".
24+
- The strings "nat" and "tan" are anagrams as they can be rearranged to form each other.
25+
- The strings "ate", "eat", and "tea" are anagrams as they can be rearranged to form each other.
26+
```
27+
28+
### Example 2:
29+
30+
```
31+
Input: strs = [""]
32+
Output: [[""]]
33+
```
34+
35+
### Example 3:
36+
37+
```
38+
Input: strs = ["a"]
39+
Output: [["a"]]
40+
```
41+
42+
## Constraints
43+
44+
- `1 <= strs.length <= 10^4`
45+
- `0 <= strs[i].length <= 100`
46+
- `strs[i]` consists of lowercase English letters.

leetcode/group_anagrams/__init__.py

Whitespace-only changes.

leetcode/group_anagrams/helpers.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
def run_group_anagrams(solution_class: type, strs: list[str]):
2+
implementation = solution_class()
3+
return implementation.group_anagrams(strs)
4+
5+
6+
def assert_group_anagrams(result: list[list[str]], expected: list[list[str]]) -> bool:
7+
# Sort both result and expected for comparison since order doesn't matter
8+
result_sorted = [sorted(group) for group in result]
9+
expected_sorted = [sorted(group) for group in expected]
10+
result_sorted.sort()
11+
expected_sorted.sort()
12+
assert result_sorted == expected_sorted
13+
return True

0 commit comments

Comments
 (0)