From e3b7a1a970eb571ac6221e1ae2353568aef8475d Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Fri, 13 Jun 2025 18:22:07 +0900 Subject: [PATCH 1/7] Solve 49_group_anagrams_medium --- 49_group_anagrams_medium/README.md | 37 ++++++++++++++++++++++ 49_group_anagrams_medium/char_count_key.py | 30 ++++++++++++++++++ 49_group_anagrams_medium/step1.py | 24 ++++++++++++++ 49_group_anagrams_medium/step2.py | 27 ++++++++++++++++ 49_group_anagrams_medium/step3.py | 24 ++++++++++++++ 5 files changed, 142 insertions(+) create mode 100644 49_group_anagrams_medium/README.md create mode 100644 49_group_anagrams_medium/char_count_key.py create mode 100644 49_group_anagrams_medium/step1.py create mode 100644 49_group_anagrams_medium/step2.py create mode 100644 49_group_anagrams_medium/step3.py diff --git a/49_group_anagrams_medium/README.md b/49_group_anagrams_medium/README.md new file mode 100644 index 0000000..34cce64 --- /dev/null +++ b/49_group_anagrams_medium/README.md @@ -0,0 +1,37 @@ +# 問題へのリンク + +[Group Anagrams - LeetCode](https://leetcode.com/problems/group-anagrams/) + +# 言語 +Python + + +# 自分の解法 +`strs`の各文字列を順番に走査し、各文字列をソートした結果をキーとして、ハッシュマップ`anagram_groups: dict[str, list[str]]`に格納する。 +アナグラム同士は、同じソート結果を持つため、同じキーに格納される。 + +`strs`の配列の長さを`n`、各文字列の長さの最大値を`m`とする。 +本問では、`0<= n <= 10^4`、`0 <= m <= 100`である。 +- 時間計算量:`O(n * m log(m))` +- 空間計算量:`O(n * m)` + + + +## step2 +- ハッシュマップを`anagram_groups`と命名。 +- キーを生成する処理を`generate_anagram_key`関数に切り出す。 +- ハッシュマップのキーを`canonical_key`と命名。(アナグラムの正規形を表すキー、の意) + +# 別解・模範解答(`char_count_key.py`) +もし、`strs`の各文字列の長さが長い場合、ソートにかかる時間が大きくなるため、キーを文字列のカウントのタプルにする方法も考えられる。 +ここで、ハッシュマップのキーに使えるのは、イミュータブルなオブジェクトである必要があるため、リストではなくタプルを使う。 + + +`strs`の配列の長さを`n`、各文字列の長さの最大値を`m`、各文字列の含む文字の種類数を`k`とすると +本問では、`k= 26`(英小文字のみ)である。 +- 時間計算量:`O(n * k)` +- 空間計量:`O(n * m)` + +# 次に解く問題の予告 +- [Implement Trie (Prefix Tree) - LeetCode](https://leetcode.com/problems/implement-trie-prefix-tree/description/) +- [Is Subsequence - LeetCode](https://leetcode.com/problems/is-subsequence/description/) diff --git a/49_group_anagrams_medium/char_count_key.py b/49_group_anagrams_medium/char_count_key.py new file mode 100644 index 0000000..075487f --- /dev/null +++ b/49_group_anagrams_medium/char_count_key.py @@ -0,0 +1,30 @@ +# +# @lc app=leetcode id=49 lang=python3 +# +# [49] Group Anagrams +# + + +# @lc code=start +from collections import defaultdict + + +class Solution: + def groupAnagrams(self, words: list[str]) -> list[list[str]]: + # manage anagrams as a hashmap whose key is "canonical key" and value is a list of anagrams + anagram_groups: dict[tuple[str], list[str]] = defaultdict(list) + + for word in words: + canonical_key = self.generate_anagram_key(word) + anagram_groups[canonical_key].append(word) + return list(anagram_groups.values()) + + def generate_anagram_key(self, word: str) -> tuple[str]: + char_count = [0] * 26 + for char in word: + order = ord(char) - ord("a") + char_count[order] += 1 + return tuple(char_count) + + +# @lc code=end diff --git a/49_group_anagrams_medium/step1.py b/49_group_anagrams_medium/step1.py new file mode 100644 index 0000000..247f658 --- /dev/null +++ b/49_group_anagrams_medium/step1.py @@ -0,0 +1,24 @@ +# +# @lc app=leetcode id=49 lang=python3 +# +# [49] Group Anagrams +# + + +# @lc code=start +from collections import defaultdict + + +class Solution: + def groupAnagrams(self, strs: list[str]) -> list[list[str]]: + + # hasmap: standardized key -> list of anagrams + hashmap = defaultdict(list) + for word in strs: + key = "".join(sorted(word)) + hashmap[key].append(word) + + return [anagrams for anagrams in hashmap.values()] + + +# @lc code=end diff --git a/49_group_anagrams_medium/step2.py b/49_group_anagrams_medium/step2.py new file mode 100644 index 0000000..3fdd4bf --- /dev/null +++ b/49_group_anagrams_medium/step2.py @@ -0,0 +1,27 @@ +# +# @lc app=leetcode id=49 lang=python3 +# +# [49] Group Anagrams +# + + +# @lc code=start +from collections import defaultdict + + +class Solution: + def groupAnagrams(self, words: list[str]) -> list[list[str]]: + # manage anagrams as a hashmap whose key is "canonical key" and value is a list of anagrams + anagram_groups: dict[str, list[str]] = defaultdict(list) + + for word in words: + canonical_key: str = self.generate_anagram_key(word) + anagram_groups[canonical_key].append(word) + return list(anagram_groups.values()) + + # canonical key of a word is generated by sorting each characters in it + def generate_anagram_key(word: str) -> str: + return "".join(sorted(word)) + + +# @lc code=end diff --git a/49_group_anagrams_medium/step3.py b/49_group_anagrams_medium/step3.py new file mode 100644 index 0000000..affed1f --- /dev/null +++ b/49_group_anagrams_medium/step3.py @@ -0,0 +1,24 @@ +# +# @lc app=leetcode id=49 lang=python3 +# +# [49] Group Anagrams +# + + +# @lc code=start +from collections import defaultdict + + +class Solution: + def groupAnagrams(self, strs: list[str]) -> list[list[str]]: + anagram_groups = defaultdict(list) + for word in strs: + canonical_key = self.generate_anagram_key(word) + anagram_groups[canonical_key].append(word) + return list(anagram_groups.values()) + + def generate_anagram_key(self, word: str) -> str: + return "".join(sorted(word)) + + +# @lc code=end From 8ae64b43f2b5d0f4c688279ca211857f30d38bfb Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Fri, 13 Jun 2025 22:35:48 +0900 Subject: [PATCH 2/7] Solve 208_implement_trie_prefix_tree_medium --- .../README.md | 48 ++++++++++++++ .../step1.py | 65 +++++++++++++++++++ .../step2.py | 64 ++++++++++++++++++ .../step2_iterative.py | 64 ++++++++++++++++++ 4 files changed, 241 insertions(+) create mode 100644 208_implement_trie_prefix_tree_medium/README.md create mode 100644 208_implement_trie_prefix_tree_medium/step1.py create mode 100644 208_implement_trie_prefix_tree_medium/step2.py create mode 100644 208_implement_trie_prefix_tree_medium/step2_iterative.py diff --git a/208_implement_trie_prefix_tree_medium/README.md b/208_implement_trie_prefix_tree_medium/README.md new file mode 100644 index 0000000..52189a7 --- /dev/null +++ b/208_implement_trie_prefix_tree_medium/README.md @@ -0,0 +1,48 @@ +# 問題へのリンク + +[Implement Trie (Prefix Tree) - LeetCode](https://leetcode.com/problems/implement-trie-prefix-tree/description/) + +# 言語 +Python + +# 問題の概要 +prefix tree(トライ木)を実装する問題。 +3つのメソッドを実装する。 +- `insert(word: str)`: 単語を挿入する。 +- `search(word: str)`: 単語が存在するかを確認する。 +- `startsWith(prefix: str)`: 単語が指定の接頭辞で始まるかを確認する。 + +# 自分の解法 + +`Trie`クラスには`char: str`と`children: dict[str, Trie]`というフィールドを持たせる。また、`is_final_char: bool`を持たせることで、単語の終端を示す。これにより、`apple`と`app`などの単語を区別できる。 + +走査は、`children`を辿っていく。再帰関数を使うことで、単語の各文字を順に確認していく。こうして`word`や`prefix`の最後の文字まで到達できれば、単語や接頭辞が存在することが確認できる。 + +`Trie`クラス自体が保持するデータは、各入力文字列の長さを$n_1, n_2, ...$とすると、最悪ケースでは$O(\sum n_i)$の空間を使用する。(重複が多ければ、より少なくなる。) + +各メソッドについて、`prefix`や`word`の長さを$n$とすると、以下のような時間計算量と空間計算量になる。 + +- 時間計算量:$O(n)$ +- 空間計算量:$O(n)$ + - 再帰関数による実装のせいで各メソッドの呼び出しのたびにサイズ$n$のスタックを使用する。 + +## step2 +- `startsWith`と`search`の実装で大きく重複していた処理を`_find_node_with`メソッドに切り出す。 + - `prefix:str`を引数に取り、`Trie`のノードを返す。 + - `prefix`の文字列が存在しない場合は`None`を返す。 +- `search`メソッドは、`_find_node_with`を使って、単語の終端であるかを確認する。 +- `startsWith`メソッドは、`_find_node_with`を使って、接頭辞が存在するかを確認する。 + +- `head`, `tail`の変数名を`first_char`, `trailing_chars`に変更。 + +## step3 + +# 別解・模範解答 +再帰関数を使わずに、`children`を辿るループで実装する。 +- 時間計算量:$O(n)$ +- 空間計算量:$O(1)$ + - 再帰関数を使わずに、`children`を辿るループで実装する。 + +# 次に解く問題の予告 +- [Subsets - LeetCode](https://leetcode.com/problems/subsets/) +- [Is Subsequence - LeetCode](https://leetcode.com/problems/is-subsequence/description/) diff --git a/208_implement_trie_prefix_tree_medium/step1.py b/208_implement_trie_prefix_tree_medium/step1.py new file mode 100644 index 0000000..0b4c58a --- /dev/null +++ b/208_implement_trie_prefix_tree_medium/step1.py @@ -0,0 +1,65 @@ +# +# @lc app=leetcode id=208 lang=python3 +# +# [208] Implement Trie (Prefix Tree) +# + + +# %% +# @lc code=start +class Trie: + def __init__(self, root=""): + self.root = root + self.children: dict[str, Trie] = {} # character -> Trie Tree + self.is_terminal = False + + def insert(self, word: str) -> None: + if not word: + self.is_terminal = True + return + + head, tail = word[0], word[1:] + if head not in self.children: + self.children[head] = Trie(root=head) + child: Trie = self.children[head] + child.insert(tail) + + def search(self, word: str) -> bool: + if not word: + return self.is_terminal + head, tail = word[0], word[1:] + if head not in self.children: + return False + else: + child: Trie = self.children[head] + return child.search(tail) + + def startsWith(self, prefix: str) -> bool: + if not prefix: + return True + head, tail = prefix[0], prefix[1:] + if head not in self.children: + return False + child: Trie = self.children[head] + return child.startsWith(tail) + + # def __str__(self): + # # show the root and children in a hierarchical way + # def _str(t: Trie, level=0): + # result = " " * level + t.root + "\n" + # for child in t.children.values(): + # result += _str(child, level + 1) + # return result + + # return _str(self) + + +# %% + +# %% +# Your Trie object will be instantiated and called as such: +# obj = Trie() +# obj.insert(word) +# param_2 = obj.search(word) +# param_3 = obj.startsWith(prefix) +# @lc code=end diff --git a/208_implement_trie_prefix_tree_medium/step2.py b/208_implement_trie_prefix_tree_medium/step2.py new file mode 100644 index 0000000..0ab2c2d --- /dev/null +++ b/208_implement_trie_prefix_tree_medium/step2.py @@ -0,0 +1,64 @@ +# +# @lc app=leetcode id=208 lang=python3 +# +# [208] Implement Trie (Prefix Tree) +# + + +# @lc code=start + + +class Trie: + def __init__(self): + self.char = "" + self.children: dict[str, Trie] = {} # character -> Trie Tree + self.is_final_char = False + + # TC: O(len(word)) + # SC: O(len(word)) + def insert(self, word: str) -> None: + if not word: + self.is_final_char = True + return + + first_char, trailing_chars = word[0], word[1:] + if first_char not in self.children: + self.children[first_char] = Trie() + self.children[first_char].char = first_char + child_node: Trie = self.children[first_char] + child_node.insert(trailing_chars) + + def search(self, word: str) -> bool: + final_node: Trie = self._find_prefix_node(word) + # word is not found + if final_node is None: + return False + # no word end at the final node + elif not final_node.is_final_char: + return False + # word end at the final node + else: + return True + + def startsWith(self, prefix: str) -> bool: + final_node: Trie = self._find_prefix_node(prefix) + return final_node is not None + + # TC: O(len(prefix)) + # SC: O(len(prefix)) + def _find_prefix_node(self, prefix: str) -> "Trie": + if not prefix: + return self + first_char, trailing_chars = prefix[0], prefix[1:] + if first_char not in self.children: + return None + child_node: Trie = self.children[first_char] + return child_node._find_prefix_node(trailing_chars) + + +# Your Trie object will be instantiated and called as such: +# obj = Trie() +# obj.insert(word) +# param_2 = obj.search(word) +# param_3 = obj.startsWith(prefix) +# @lc code=end diff --git a/208_implement_trie_prefix_tree_medium/step2_iterative.py b/208_implement_trie_prefix_tree_medium/step2_iterative.py new file mode 100644 index 0000000..a248cf0 --- /dev/null +++ b/208_implement_trie_prefix_tree_medium/step2_iterative.py @@ -0,0 +1,64 @@ +# +# @lc app=leetcode id=208 lang=python3 +# +# [208] Implement Trie (Prefix Tree) +# + + +# %% +# @lc code=start +class Trie: + def __init__(self): + self.char = "" + self.children: dict[str, Trie] = {} + self.is_final_char = False + + def insert(self, word: str) -> None: + if not word: + return + + node: Trie = self + for char in word: + if char not in node.children: + node.children[char] = Trie() + node.children[char].char = char + node = node.children[char] + node.is_final_char = True + return + + def search(self, word: str) -> bool: + final_node = self._find_node_with(word) + if final_node is None: + return False + elif not final_node.is_final_char: + return False + else: + return True + + def startsWith(self, prefix: str) -> bool: + final_node = self._find_node_with(prefix) + return final_node is not None + + def _find_node_with(self, prefix: str) -> "Trie": + if not prefix: + return self + + node: Trie = self + for i, char in enumerate(prefix): + if not node: + return None + elif char not in node.children: + return None + node = node.children[char] + return node + + +# %% + +# %% +# Your Trie object will be instantiated and called as such: +# obj = Trie() +# obj.insert(word) +# param_2 = obj.search(word) +# param_3 = obj.startsWith(prefix) +# @lc code=end From 4c9c0266d60441a95d530b1b369309e9943f6335 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Sat, 14 Jun 2025 09:52:04 +0900 Subject: [PATCH 3/7] Update step2; Add TrieNode class and defaultdict --- .../README.md | 4 ++- .../step2.py | 8 ++--- .../step2_iterative.py | 34 +++++++++++-------- 3 files changed, 24 insertions(+), 22 deletions(-) diff --git a/208_implement_trie_prefix_tree_medium/README.md b/208_implement_trie_prefix_tree_medium/README.md index 52189a7..4c7f830 100644 --- a/208_implement_trie_prefix_tree_medium/README.md +++ b/208_implement_trie_prefix_tree_medium/README.md @@ -32,6 +32,7 @@ prefix tree(トライ木)を実装する問題。 - `prefix`の文字列が存在しない場合は`None`を返す。 - `search`メソッドは、`_find_node_with`を使って、単語の終端であるかを確認する。 - `startsWith`メソッドは、`_find_node_with`を使って、接頭辞が存在するかを確認する。 +- `children = defaultdict(Trie)`を使って、`children`の初期化やキーの存在確認を簡潔にする。 - `head`, `tail`の変数名を`first_char`, `trailing_chars`に変更。 @@ -42,7 +43,8 @@ prefix tree(トライ木)を実装する問題。 - 時間計算量:$O(n)$ - 空間計算量:$O(1)$ - 再帰関数を使わずに、`children`を辿るループで実装する。 - +- `TrieNode`クラスを作成し、`Trie`のノードを表現する。 + - `char: str`, `children: dict[str, TrieNode]`, `is_final_char: bool`のフィールドを持つ。 # 次に解く問題の予告 - [Subsets - LeetCode](https://leetcode.com/problems/subsets/) - [Is Subsequence - LeetCode](https://leetcode.com/problems/is-subsequence/description/) diff --git a/208_implement_trie_prefix_tree_medium/step2.py b/208_implement_trie_prefix_tree_medium/step2.py index 0ab2c2d..42165a1 100644 --- a/208_implement_trie_prefix_tree_medium/step2.py +++ b/208_implement_trie_prefix_tree_medium/step2.py @@ -6,12 +6,12 @@ # @lc code=start +from collections import defaultdict class Trie: def __init__(self): - self.char = "" - self.children: dict[str, Trie] = {} # character -> Trie Tree + self.children = defaultdict(Trie) self.is_final_char = False # TC: O(len(word)) @@ -20,11 +20,7 @@ def insert(self, word: str) -> None: if not word: self.is_final_char = True return - first_char, trailing_chars = word[0], word[1:] - if first_char not in self.children: - self.children[first_char] = Trie() - self.children[first_char].char = first_char child_node: Trie = self.children[first_char] child_node.insert(trailing_chars) diff --git a/208_implement_trie_prefix_tree_medium/step2_iterative.py b/208_implement_trie_prefix_tree_medium/step2_iterative.py index a248cf0..2bb5a04 100644 --- a/208_implement_trie_prefix_tree_medium/step2_iterative.py +++ b/208_implement_trie_prefix_tree_medium/step2_iterative.py @@ -7,27 +7,33 @@ # %% # @lc code=start -class Trie: +from collections import defaultdict + + +# TrieNode has char:str, children: dict[str, TrieNode], and is_final_char +class TrieNode: def __init__(self): - self.char = "" - self.children: dict[str, Trie] = {} + self.children = defaultdict(TrieNode) self.is_final_char = False + +# Trie has root TrieNode +class Trie: + def __init__(self): + self.root = TrieNode() + def insert(self, word: str) -> None: if not word: return - node: Trie = self + node = self.root for char in word: - if char not in node.children: - node.children[char] = Trie() - node.children[char].char = char node = node.children[char] node.is_final_char = True return def search(self, word: str) -> bool: - final_node = self._find_node_with(word) + final_node: TrieNode = self._find_node_with(word) if final_node is None: return False elif not final_node.is_final_char: @@ -39,15 +45,13 @@ def startsWith(self, prefix: str) -> bool: final_node = self._find_node_with(prefix) return final_node is not None - def _find_node_with(self, prefix: str) -> "Trie": + def _find_node_with(self, prefix: str) -> TrieNode: if not prefix: - return self + return self.root - node: Trie = self - for i, char in enumerate(prefix): - if not node: - return None - elif char not in node.children: + node = self.root + for char in prefix: + if char not in node.children: return None node = node.children[char] return node From ea5300e75b255ff12546d2e8195d5e2667cb95f4 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Sat, 14 Jun 2025 10:09:33 +0900 Subject: [PATCH 4/7] Add step3 --- .../step3.py | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 208_implement_trie_prefix_tree_medium/step3.py diff --git a/208_implement_trie_prefix_tree_medium/step3.py b/208_implement_trie_prefix_tree_medium/step3.py new file mode 100644 index 0000000..b6657e8 --- /dev/null +++ b/208_implement_trie_prefix_tree_medium/step3.py @@ -0,0 +1,71 @@ +# +# @lc app=leetcode id=208 lang=python3 +# +# [208] Implement Trie (Prefix Tree) +# + + +# %% +# @lc code=start +# TrieNode has char:str, children: dict[str, TrieNode], and is_final_char +class TrieNode: + def __init__(self): + self.char = "" + self.children: dict[str, TrieNode] = {} + self.is_final_char = False + + +# Trie has root TrieNode +class Trie: + def __init__(self): + self.root = TrieNode() + + def insert(self, word: str) -> None: + if not word: + return + + node = self.root + for char in word: + if char not in node.children: + child_node = TrieNode() + child_node.char = char + node.children[char] = child_node + node = node.children[char] + node.is_final_char = True + return + + def search(self, word: str) -> bool: + final_node: TrieNode = self._find_node_with(word) + if final_node is None: + return False + elif not final_node.is_final_char: + return False + else: + return True + + def startsWith(self, prefix: str) -> bool: + final_node = self._find_node_with(prefix) + return final_node is not None + + def _find_node_with(self, prefix: str) -> TrieNode: + if not prefix: + return None + + node = self.root + for char in prefix: + if char not in node.children: + return None + node = node.children[char] + + return node + + +# %% + +# %% +# Your Trie object will be instantiated and called as such: +# obj = Trie() +# obj.insert(word) +# param_2 = obj.search(word) +# param_3 = obj.startsWith(prefix) +# @lc code=end From 0fe8c429b1d5c92fe87f1a781ebba98c1ab9e876 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Sat, 14 Jun 2025 10:13:30 +0900 Subject: [PATCH 5/7] Fix README for Trie implementation --- .../README.md | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/208_implement_trie_prefix_tree_medium/README.md b/208_implement_trie_prefix_tree_medium/README.md index 4c7f830..62be780 100644 --- a/208_implement_trie_prefix_tree_medium/README.md +++ b/208_implement_trie_prefix_tree_medium/README.md @@ -9,7 +9,7 @@ Python prefix tree(トライ木)を実装する問題。 3つのメソッドを実装する。 - `insert(word: str)`: 単語を挿入する。 -- `search(word: str)`: 単語が存在するかを確認する。 +- `search(prefix: str)`: 単語が存在するかを確認する。 - `startsWith(prefix: str)`: 単語が指定の接頭辞で始まるかを確認する。 # 自分の解法 @@ -18,15 +18,17 @@ prefix tree(トライ木)を実装する問題。 走査は、`children`を辿っていく。再帰関数を使うことで、単語の各文字を順に確認していく。こうして`word`や`prefix`の最後の文字まで到達できれば、単語や接頭辞が存在することが確認できる。 -`Trie`クラス自体が保持するデータは、各入力文字列の長さを$n_1, n_2, ...$とすると、最悪ケースでは$O(\sum n_i)$の空間を使用する。(重複が多ければ、より少なくなる。) +`Trie`クラス自体が保持するデータは、各入力文字列の長さを`n_1, n_2, ...`とすると、最悪ケースでは`O(n_1+n_2+...)`の空間を使用する。(重複が多ければ、より少なくなる。) -各メソッドについて、`prefix`や`word`の長さを$n$とすると、以下のような時間計算量と空間計算量になる。 +各メソッドについて、`prefix`や`word`の長さを`n`とすると、以下のような時間計算量と空間計算量になる。 -- 時間計算量:$O(n)$ -- 空間計算量:$O(n)$ - - 再帰関数による実装のせいで各メソッドの呼び出しのたびにサイズ$n$のスタックを使用する。 +- 時間計算量:`O(n)` +- 空間計算量:`O(n)` + - 再帰関数による実装のせいで各メソッドの呼び出しのたびにサイズ`n`のスタックを使用する。 ## step2 +- `char`フィールドを削除 + - 子ノードのキーとして文字を使用するため、`char`フィールドは不要。 - `startsWith`と`search`の実装で大きく重複していた処理を`_find_node_with`メソッドに切り出す。 - `prefix:str`を引数に取り、`Trie`のノードを返す。 - `prefix`の文字列が存在しない場合は`None`を返す。 @@ -36,12 +38,14 @@ prefix tree(トライ木)を実装する問題。 - `head`, `tail`の変数名を`first_char`, `trailing_chars`に変更。 + ## step3 +- v1. 16minかかってしまったのでやり直し # 別解・模範解答 再帰関数を使わずに、`children`を辿るループで実装する。 -- 時間計算量:$O(n)$ -- 空間計算量:$O(1)$ +- 時間計算量:`O(n)` +- 空間計算量:`O(1)` - 再帰関数を使わずに、`children`を辿るループで実装する。 - `TrieNode`クラスを作成し、`Trie`のノードを表現する。 - `char: str`, `children: dict[str, TrieNode]`, `is_final_char: bool`のフィールドを持つ。 From b828354f24c4e60d11f7df15d9cc08c00a97721e Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Mon, 16 Jun 2025 18:42:54 +0900 Subject: [PATCH 6/7] Remove unused files related to Group Anagrams implementation --- 49_group_anagrams_medium/README.md | 37 ---------------------- 49_group_anagrams_medium/char_count_key.py | 30 ------------------ 49_group_anagrams_medium/step1.py | 24 -------------- 49_group_anagrams_medium/step2.py | 27 ---------------- 49_group_anagrams_medium/step3.py | 24 -------------- 5 files changed, 142 deletions(-) delete mode 100644 49_group_anagrams_medium/README.md delete mode 100644 49_group_anagrams_medium/char_count_key.py delete mode 100644 49_group_anagrams_medium/step1.py delete mode 100644 49_group_anagrams_medium/step2.py delete mode 100644 49_group_anagrams_medium/step3.py diff --git a/49_group_anagrams_medium/README.md b/49_group_anagrams_medium/README.md deleted file mode 100644 index 34cce64..0000000 --- a/49_group_anagrams_medium/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# 問題へのリンク - -[Group Anagrams - LeetCode](https://leetcode.com/problems/group-anagrams/) - -# 言語 -Python - - -# 自分の解法 -`strs`の各文字列を順番に走査し、各文字列をソートした結果をキーとして、ハッシュマップ`anagram_groups: dict[str, list[str]]`に格納する。 -アナグラム同士は、同じソート結果を持つため、同じキーに格納される。 - -`strs`の配列の長さを`n`、各文字列の長さの最大値を`m`とする。 -本問では、`0<= n <= 10^4`、`0 <= m <= 100`である。 -- 時間計算量:`O(n * m log(m))` -- 空間計算量:`O(n * m)` - - - -## step2 -- ハッシュマップを`anagram_groups`と命名。 -- キーを生成する処理を`generate_anagram_key`関数に切り出す。 -- ハッシュマップのキーを`canonical_key`と命名。(アナグラムの正規形を表すキー、の意) - -# 別解・模範解答(`char_count_key.py`) -もし、`strs`の各文字列の長さが長い場合、ソートにかかる時間が大きくなるため、キーを文字列のカウントのタプルにする方法も考えられる。 -ここで、ハッシュマップのキーに使えるのは、イミュータブルなオブジェクトである必要があるため、リストではなくタプルを使う。 - - -`strs`の配列の長さを`n`、各文字列の長さの最大値を`m`、各文字列の含む文字の種類数を`k`とすると -本問では、`k= 26`(英小文字のみ)である。 -- 時間計算量:`O(n * k)` -- 空間計量:`O(n * m)` - -# 次に解く問題の予告 -- [Implement Trie (Prefix Tree) - LeetCode](https://leetcode.com/problems/implement-trie-prefix-tree/description/) -- [Is Subsequence - LeetCode](https://leetcode.com/problems/is-subsequence/description/) diff --git a/49_group_anagrams_medium/char_count_key.py b/49_group_anagrams_medium/char_count_key.py deleted file mode 100644 index 075487f..0000000 --- a/49_group_anagrams_medium/char_count_key.py +++ /dev/null @@ -1,30 +0,0 @@ -# -# @lc app=leetcode id=49 lang=python3 -# -# [49] Group Anagrams -# - - -# @lc code=start -from collections import defaultdict - - -class Solution: - def groupAnagrams(self, words: list[str]) -> list[list[str]]: - # manage anagrams as a hashmap whose key is "canonical key" and value is a list of anagrams - anagram_groups: dict[tuple[str], list[str]] = defaultdict(list) - - for word in words: - canonical_key = self.generate_anagram_key(word) - anagram_groups[canonical_key].append(word) - return list(anagram_groups.values()) - - def generate_anagram_key(self, word: str) -> tuple[str]: - char_count = [0] * 26 - for char in word: - order = ord(char) - ord("a") - char_count[order] += 1 - return tuple(char_count) - - -# @lc code=end diff --git a/49_group_anagrams_medium/step1.py b/49_group_anagrams_medium/step1.py deleted file mode 100644 index 247f658..0000000 --- a/49_group_anagrams_medium/step1.py +++ /dev/null @@ -1,24 +0,0 @@ -# -# @lc app=leetcode id=49 lang=python3 -# -# [49] Group Anagrams -# - - -# @lc code=start -from collections import defaultdict - - -class Solution: - def groupAnagrams(self, strs: list[str]) -> list[list[str]]: - - # hasmap: standardized key -> list of anagrams - hashmap = defaultdict(list) - for word in strs: - key = "".join(sorted(word)) - hashmap[key].append(word) - - return [anagrams for anagrams in hashmap.values()] - - -# @lc code=end diff --git a/49_group_anagrams_medium/step2.py b/49_group_anagrams_medium/step2.py deleted file mode 100644 index 3fdd4bf..0000000 --- a/49_group_anagrams_medium/step2.py +++ /dev/null @@ -1,27 +0,0 @@ -# -# @lc app=leetcode id=49 lang=python3 -# -# [49] Group Anagrams -# - - -# @lc code=start -from collections import defaultdict - - -class Solution: - def groupAnagrams(self, words: list[str]) -> list[list[str]]: - # manage anagrams as a hashmap whose key is "canonical key" and value is a list of anagrams - anagram_groups: dict[str, list[str]] = defaultdict(list) - - for word in words: - canonical_key: str = self.generate_anagram_key(word) - anagram_groups[canonical_key].append(word) - return list(anagram_groups.values()) - - # canonical key of a word is generated by sorting each characters in it - def generate_anagram_key(word: str) -> str: - return "".join(sorted(word)) - - -# @lc code=end diff --git a/49_group_anagrams_medium/step3.py b/49_group_anagrams_medium/step3.py deleted file mode 100644 index affed1f..0000000 --- a/49_group_anagrams_medium/step3.py +++ /dev/null @@ -1,24 +0,0 @@ -# -# @lc app=leetcode id=49 lang=python3 -# -# [49] Group Anagrams -# - - -# @lc code=start -from collections import defaultdict - - -class Solution: - def groupAnagrams(self, strs: list[str]) -> list[list[str]]: - anagram_groups = defaultdict(list) - for word in strs: - canonical_key = self.generate_anagram_key(word) - anagram_groups[canonical_key].append(word) - return list(anagram_groups.values()) - - def generate_anagram_key(self, word: str) -> str: - return "".join(sorted(word)) - - -# @lc code=end From a4cc78aa4952c30e787725d85a13a137020bd229 Mon Sep 17 00:00:00 2001 From: Kaichi-Irie Date: Mon, 16 Jun 2025 19:14:55 +0900 Subject: [PATCH 7/7] Refactor Trie implementation: simplify methods and update type hints for clarity --- .../step1.py | 10 ---- .../step2.py | 8 ++-- .../step2_iterative.py | 4 +- .../step3.py | 47 +++++++------------ 4 files changed, 23 insertions(+), 46 deletions(-) diff --git a/208_implement_trie_prefix_tree_medium/step1.py b/208_implement_trie_prefix_tree_medium/step1.py index 0b4c58a..6fecc6d 100644 --- a/208_implement_trie_prefix_tree_medium/step1.py +++ b/208_implement_trie_prefix_tree_medium/step1.py @@ -43,16 +43,6 @@ def startsWith(self, prefix: str) -> bool: child: Trie = self.children[head] return child.startsWith(tail) - # def __str__(self): - # # show the root and children in a hierarchical way - # def _str(t: Trie, level=0): - # result = " " * level + t.root + "\n" - # for child in t.children.values(): - # result += _str(child, level + 1) - # return result - - # return _str(self) - # %% diff --git a/208_implement_trie_prefix_tree_medium/step2.py b/208_implement_trie_prefix_tree_medium/step2.py index 42165a1..9cbca06 100644 --- a/208_implement_trie_prefix_tree_medium/step2.py +++ b/208_implement_trie_prefix_tree_medium/step2.py @@ -25,7 +25,7 @@ def insert(self, word: str) -> None: child_node.insert(trailing_chars) def search(self, word: str) -> bool: - final_node: Trie = self._find_prefix_node(word) + final_node: Trie | None = self._find_prefix_node(word) # word is not found if final_node is None: return False @@ -37,12 +37,14 @@ def search(self, word: str) -> bool: return True def startsWith(self, prefix: str) -> bool: - final_node: Trie = self._find_prefix_node(prefix) + final_node: Trie | None = self._find_prefix_node(prefix) return final_node is not None # TC: O(len(prefix)) # SC: O(len(prefix)) - def _find_prefix_node(self, prefix: str) -> "Trie": + from typing import Optional + + def _find_prefix_node(self, prefix: str) -> Optional["Trie"]: if not prefix: return self first_char, trailing_chars = prefix[0], prefix[1:] diff --git a/208_implement_trie_prefix_tree_medium/step2_iterative.py b/208_implement_trie_prefix_tree_medium/step2_iterative.py index 2bb5a04..568aba7 100644 --- a/208_implement_trie_prefix_tree_medium/step2_iterative.py +++ b/208_implement_trie_prefix_tree_medium/step2_iterative.py @@ -33,7 +33,7 @@ def insert(self, word: str) -> None: return def search(self, word: str) -> bool: - final_node: TrieNode = self._find_node_with(word) + final_node: TrieNode | None = self._find_node_with(word) if final_node is None: return False elif not final_node.is_final_char: @@ -45,7 +45,7 @@ def startsWith(self, prefix: str) -> bool: final_node = self._find_node_with(prefix) return final_node is not None - def _find_node_with(self, prefix: str) -> TrieNode: + def _find_node_with(self, prefix: str) -> TrieNode | None: if not prefix: return self.root diff --git a/208_implement_trie_prefix_tree_medium/step3.py b/208_implement_trie_prefix_tree_medium/step3.py index b6657e8..fc406b1 100644 --- a/208_implement_trie_prefix_tree_medium/step3.py +++ b/208_implement_trie_prefix_tree_medium/step3.py @@ -8,11 +8,13 @@ # %% # @lc code=start # TrieNode has char:str, children: dict[str, TrieNode], and is_final_char +from collections import defaultdict + + class TrieNode: def __init__(self): - self.char = "" - self.children: dict[str, TrieNode] = {} - self.is_final_char = False + self.children: dict[str, TrieNode] = defaultdict(TrieNode) + self.word_ends_here = False # Trie has root TrieNode @@ -21,43 +23,26 @@ def __init__(self): self.root = TrieNode() def insert(self, word: str) -> None: - if not word: - return - - node = self.root + node: TrieNode = self.root for char in word: - if char not in node.children: - child_node = TrieNode() - child_node.char = char - node.children[char] = child_node node = node.children[char] - node.is_final_char = True - return + node.word_ends_here = True def search(self, word: str) -> bool: - final_node: TrieNode = self._find_node_with(word) - if final_node is None: - return False - elif not final_node.is_final_char: - return False - else: - return True + node: TrieNode = self.root + for char in word: + if char not in node.children: + return False + node = node.children[char] + return node.word_ends_here def startsWith(self, prefix: str) -> bool: - final_node = self._find_node_with(prefix) - return final_node is not None - - def _find_node_with(self, prefix: str) -> TrieNode: - if not prefix: - return None - - node = self.root + node: TrieNode = self.root for char in prefix: if char not in node.children: - return None + return False node = node.children[char] - - return node + return True # %%