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..62be780 --- /dev/null +++ b/208_implement_trie_prefix_tree_medium/README.md @@ -0,0 +1,54 @@ +# 問題へのリンク + +[Implement Trie (Prefix Tree) - LeetCode](https://leetcode.com/problems/implement-trie-prefix-tree/description/) + +# 言語 +Python + +# 問題の概要 +prefix tree(トライ木)を実装する問題。 +3つのメソッドを実装する。 +- `insert(word: str)`: 単語を挿入する。 +- `search(prefix: 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(n_1+n_2+...)`の空間を使用する。(重複が多ければ、より少なくなる。) + +各メソッドについて、`prefix`や`word`の長さを`n`とすると、以下のような時間計算量と空間計算量になる。 + +- 時間計算量:`O(n)` +- 空間計算量:`O(n)` + - 再帰関数による実装のせいで各メソッドの呼び出しのたびにサイズ`n`のスタックを使用する。 + +## step2 +- `char`フィールドを削除 + - 子ノードのキーとして文字を使用するため、`char`フィールドは不要。 +- `startsWith`と`search`の実装で大きく重複していた処理を`_find_node_with`メソッドに切り出す。 + - `prefix:str`を引数に取り、`Trie`のノードを返す。 + - `prefix`の文字列が存在しない場合は`None`を返す。 +- `search`メソッドは、`_find_node_with`を使って、単語の終端であるかを確認する。 +- `startsWith`メソッドは、`_find_node_with`を使って、接頭辞が存在するかを確認する。 +- `children = defaultdict(Trie)`を使って、`children`の初期化やキーの存在確認を簡潔にする。 + +- `head`, `tail`の変数名を`first_char`, `trailing_chars`に変更。 + + +## step3 +- v1. 16minかかってしまったのでやり直し + +# 別解・模範解答 +再帰関数を使わずに、`children`を辿るループで実装する。 +- 時間計算量:`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/step1.py b/208_implement_trie_prefix_tree_medium/step1.py new file mode 100644 index 0000000..6fecc6d --- /dev/null +++ b/208_implement_trie_prefix_tree_medium/step1.py @@ -0,0 +1,55 @@ +# +# @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) + + +# %% + +# %% +# 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..9cbca06 --- /dev/null +++ b/208_implement_trie_prefix_tree_medium/step2.py @@ -0,0 +1,62 @@ +# +# @lc app=leetcode id=208 lang=python3 +# +# [208] Implement Trie (Prefix Tree) +# + + +# @lc code=start +from collections import defaultdict + + +class Trie: + def __init__(self): + self.children = defaultdict(Trie) + 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:] + child_node: Trie = self.children[first_char] + child_node.insert(trailing_chars) + + def search(self, word: str) -> bool: + final_node: Trie | None = 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 | None = self._find_prefix_node(prefix) + return final_node is not None + + # TC: O(len(prefix)) + # SC: O(len(prefix)) + 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:] + 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..568aba7 --- /dev/null +++ b/208_implement_trie_prefix_tree_medium/step2_iterative.py @@ -0,0 +1,68 @@ +# +# @lc app=leetcode id=208 lang=python3 +# +# [208] Implement Trie (Prefix Tree) +# + + +# %% +# @lc code=start +from collections import defaultdict + + +# TrieNode has char:str, children: dict[str, TrieNode], and is_final_char +class TrieNode: + def __init__(self): + 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 = self.root + for char in word: + node = node.children[char] + node.is_final_char = True + return + + def search(self, word: str) -> bool: + final_node: TrieNode | None = 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 | None: + if not prefix: + return self.root + + 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 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..fc406b1 --- /dev/null +++ b/208_implement_trie_prefix_tree_medium/step3.py @@ -0,0 +1,56 @@ +# +# @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 +from collections import defaultdict + + +class TrieNode: + def __init__(self): + self.children: dict[str, TrieNode] = defaultdict(TrieNode) + self.word_ends_here = False + + +# Trie has root TrieNode +class Trie: + def __init__(self): + self.root = TrieNode() + + def insert(self, word: str) -> None: + node: TrieNode = self.root + for char in word: + node = node.children[char] + node.word_ends_here = True + + def search(self, word: str) -> bool: + 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: + node: TrieNode = self.root + for char in prefix: + if char not in node.children: + return False + node = node.children[char] + return True + + +# %% + +# %% +# 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