-
Notifications
You must be signed in to change notification settings - Fork 0
Create 139. Word Break.md #39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,191 @@ | ||
| # 139. Word Break | ||
| ## STEP1 | ||
| - 何も見ずに解いてみる | ||
| - s を前から見ていき、wordDict で構成できるか確認する。 | ||
| - word の文字数を考えるべきかどうかはパターンがありそう。スライスは文字列の長さを超えても問題ないので文字数を考慮しないことにした。 | ||
| ```python | ||
| class Solution: | ||
| def wordBreak(self, s: str, wordDict: List[str]) -> bool: | ||
| is_break = [False] * (len(s) + 1) | ||
| is_break[0] = True | ||
| for i in range(len(s)): | ||
| if not is_break[i]: | ||
| continue | ||
| for word in wordDict: | ||
| if word == s[i : i + len(word)]: | ||
| is_break[i + len(word)] = True | ||
| return is_break[-1] | ||
| ``` | ||
| #### memo | ||
| 時間計算量: $O(len(s) * len(wordDict) * max(len(word)))$ | ||
| 空間計算量: $O(len(s))$ | ||
| 計算時間を見積もる。CPU の動作周波数は数 GHz 程度で、1 秒間に数十億回機械語が実行される。C++ のような高速な言語だと、1 秒間に 1~10 億ステップ程度実行できる (コンパイルして機械語に変換したときのオーバーヘッドを考慮した数字)。Python は 50 倍程度遅く、1 秒間に 200~2000 万ステップ程度実行できる。s.length = 300, wordDict.length = 1000, wordDict[i].length = 20 の時を考えると、len(s) * len(wordDict) * max(len(word)) = 6 * 10^6 なので、計算時間は 0.3 ~ 3 s 程度。 | ||
| LeetCode 上では 3 msだった。比較的単純な for 文であること、文字列に関してはネイティブなコードが走っていそう、という点で見積もりは大きくなった? | ||
|
|
||
| ## STEP2 | ||
| ### プルリクやドキュメントを参照 | ||
| - https://discord.com/channels/1084280443945353267/1225849404037009609/1245423153258299613 | ||
| - startswith が使える。スライスしないで比較ができる。 | ||
| - https://docs.python.org/3/library/stdtypes.html#str.startswith | ||
| ```python | ||
| class Solution: | ||
| def wordBreak(self, s: str, wordDict: List[str]) -> bool: | ||
| is_breakable = [False] * (len(s) + 1) | ||
| is_breakable[0] = True | ||
| for start_index in range(len(s)): | ||
| if not is_breakable[start_index]: | ||
| continue | ||
| for word in wordDict: | ||
| if s.startswith(word, start_index): | ||
| is_breakable[start_index + len(word)] = True | ||
| return is_breakable[-1] | ||
| ``` | ||
|
|
||
| - https://github.com/olsen-blue/Arai60/pull/39/files | ||
| - トップダウンでDPする書き方。STEP1 で書いたようなボトムアップの方が好み。 | ||
| ```python | ||
| import functools | ||
|
|
||
|
|
||
| class Solution: | ||
| def wordBreak(self, s: str, wordDict: List[str]) -> bool: | ||
| @functools.cache | ||
| def is_breakable(index: int) -> bool: | ||
| if index == 0: | ||
| return True | ||
| for word in wordDict: | ||
| start_index = index - len(word) | ||
| if start_index < 0: | ||
| continue | ||
| if s.startswith(word, start_index): | ||
| if is_breakable(start_index): | ||
| return True | ||
| return False | ||
|
|
||
| return is_breakable(len(s)) | ||
| ``` | ||
| - https://github.com/Mike0121/LeetCode/pull/52/files | ||
| - DFS, BFS でも書ける。 | ||
| - s の prefix が作れるかどうかを配列で管理して順番に走査するのがSTEP1の書き方で、作れる prefix を queue で管理するのが BFS。構成できるかのみを知りたい場合だとSTEP1の書き方がシンプルで好み。 | ||
| ```python | ||
| import collections | ||
|
|
||
|
|
||
| class Solution: | ||
| def wordBreak(self, s: str, wordDict: List[str]) -> bool: | ||
| start_indices = collections.deque([0]) | ||
| checked_indices = {0} | ||
| while start_indices: | ||
| start_index = start_indices.popleft() | ||
| if start_index == len(s): | ||
| return True | ||
| for word in wordDict: | ||
| next_index = start_index + len(word) | ||
| if next_index in checked_indices: | ||
| continue | ||
| if s.startswith(word, start_index): | ||
| start_indices.append(next_index) | ||
| checked_indices.add(next_index) | ||
| return False | ||
| ``` | ||
| - https://github.com/philip82148/leetcode-swejp/pull/8/files#r1864310221 | ||
| - Trie木というデータ構造を使うこともできる。 | ||
| - DP で遷移を考える時に、O(len(wordDict) * max(len(wordDict[i]))) かかっていたところを改善できる。Trie木により wordDict の単語のうち prefix が同じものをまとめて管理できる。s の文字を Trie木で辿ることでDP の遷移を O(len(s)) で処理できる。 | ||
|
|
||
| wordBreak 内で s と Trie木 をマッチングする書き方 | ||
| ```python | ||
| class TrieNode: | ||
| def __init__(self): | ||
| self.next = {} | ||
| self.is_end_word = False | ||
|
|
||
| def add_word(self, word): | ||
| node = self | ||
| for char in word: | ||
| if char not in node.next: | ||
| node.next[char] = TrieNode() | ||
| node = node.next[char] | ||
| node.is_end_word = True | ||
|
|
||
|
|
||
| class Solution: | ||
| def wordBreak(self, s: str, wordDict: List[str]) -> bool: | ||
| root = TrieNode() | ||
| for word in wordDict: | ||
| root.add_word(word) | ||
|
|
||
| is_breakable = [False] * (len(s) + 1) | ||
| is_breakable[0] = True | ||
| for start_index in range(len(s)): | ||
| if not is_breakable[start_index]: | ||
| continue | ||
| node = root | ||
| for i in range(start_index, len(s)): | ||
| if s[i] not in node.next: | ||
| break | ||
| node = node.next[s[i]] | ||
| if node.is_end_word: | ||
| is_breakable[i + 1] = True | ||
| return is_breakable[-1] | ||
| ``` | ||
|
|
||
| TrieNode.get_match_lengths を実装したパターン。こちらの方が機能が分離されていて良いと思った。 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 自分もそう思いました |
||
| ```python | ||
| class TrieNode: | ||
| def __init__(self): | ||
| self.next = {} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. https://docs.python.org/ja/3/library/functions.html#next |
||
| self.is_word_end = False | ||
|
|
||
| def add_word(self, word): | ||
| node = self | ||
| for char in word: | ||
| if char not in node.next: | ||
| node.next[char] = TrieNode() | ||
| node = node.next[char] | ||
| node.is_word_end = True | ||
|
|
||
| def get_match_lengths(self, s): | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ジェネレータにすることもできますね |
||
| result = [] | ||
| node = self | ||
| for i, char in enumerate(s): | ||
| if char not in node.next: | ||
| break | ||
| node = node.next[char] | ||
| if node.is_word_end: | ||
| result.append(i + 1) | ||
| return result | ||
|
|
||
|
|
||
| class Solution: | ||
| def wordBreak(self, s: str, wordDict: List[str]) -> bool: | ||
| root = TrieNode() | ||
| for word in wordDict: | ||
| root.add_word(word) | ||
|
|
||
| is_breakable = [False] * (len(s) + 1) | ||
| is_breakable[0] = True | ||
| for start_index in range(len(s)): | ||
| if not is_breakable[start_index]: | ||
| continue | ||
| match_lengths = root.get_match_lengths(s[start_index:]) | ||
| for length in match_lengths: | ||
| is_breakable[start_index + length] = True | ||
| return is_breakable[-1] | ||
| ``` | ||
| ## STEP3 | ||
| ### 3回ミスなく書く | ||
| ```python | ||
| class Solution: | ||
| def wordBreak(self, s: str, wordDict: List[str]) -> bool: | ||
| is_breakable = [False] * (len(s) + 1) | ||
| is_breakable[0] = True | ||
| for start in range(len(s)): | ||
| if not is_breakable[start]: | ||
| continue | ||
| for word in wordDict: | ||
| if s.startswith(word, start): | ||
| is_breakable[start + len(word)] = True | ||
| return is_breakable[-1] | ||
| ``` | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 読みやすかったです。いいと思います。 |
||
|
|
||
| 3分,2分,2分で3回Accept | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
単独のnodeの部分と、trieの木の部分とでクラスを分ける方が自分の好みです。