-
Notifications
You must be signed in to change notification settings - Fork 0
Solved Arai60/139. Word Break #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,158 @@ | ||
## Step 1. Initial Solution | ||
|
||
- 前からどんどん処理して残りの処理を委託するのが楽そうに見えるので再帰で試す | ||
- 長い文字列に対してTLE → splitにかかる時間の問題? | ||
|
||
```python | ||
class Solution: | ||
def wordBreak(self, s: str, wordDict: List[str]) -> bool: | ||
def isWordBreakable(s: str) -> bool: | ||
if s in wordDict: | ||
return True | ||
for i in range(len(s)): | ||
if s[:i+1] in wordDict and isWordBreakable(s[i+1:]): | ||
return True | ||
return False | ||
return isWordBreakable(s) | ||
``` | ||
|
||
- いくつか処理時間を短くする工夫を追加したがTLE | ||
- 計算量の見積もりが難しかったが以下のように予想 | ||
- 関数内関数の実行回数は最悪の場合k^n | ||
- かなりざっくりした見積もりなので流石にもっと少ないがオーダーはこれに近いので到底無理そう | ||
- 20^300 ≈ 10^390 | ||
|
||
```python | ||
# n: len(s), m: len(wordDict), k: max_word_length | ||
class Solution: | ||
def wordBreak(self, s: str, wordDict: List[str]) -> bool: | ||
max_word_length = len(max(wordDict, key=len)) # O(m) | ||
wordDict = set(wordDict) # O(m) | ||
def isWordBreakableAfter(start: int) -> bool: | ||
if len(s) - start <= max_word_length and s[start:] in wordDict: # O(1) or O(k) | ||
return True | ||
word_length = min(max_word_length, len(s) - start) | ||
for i in range(word_length, 0, -1): # O(k + (k + F(n-start)) | ||
if s[start:start + i] in wordDict and isWordBreakableAfter(start + i): # O(k) or O(k + F) | ||
return True | ||
return False | ||
return isWordBreakableAfter(0) # O(m + k * k * ...) < O(k^n) | ||
``` | ||
|
||
- 1時間くらい経ってしまったので答えを確認して以下の方針で実装 | ||
- wordDictに関してfor文を回す | ||
- 各インデックスにそこまで行けるかを記録(DP) | ||
|
||
```python | ||
class Solution: | ||
def wordBreak(self, s: str, wordDict: List[str]) -> bool: | ||
isWordBreakableTo = [True] + [False] * len(s) | ||
for i in range(len(s)): | ||
if not isWordBreakableTo[i]: | ||
continue | ||
for word in wordDict: | ||
word_length = len(word) | ||
if s[i:i + word_length] == word: | ||
isWordBreakableTo[i + word_length] = True | ||
return isWordBreakableTo[-1] | ||
``` | ||
|
||
### Complexity Analysis | ||
|
||
- 時間計算量:O(n * m * k) | ||
- 文字列の長さ n * 辞書の長さ m * 単語の長さ k | ||
- それぞれ最大 300 * 1000 * 20で 6 * 10^6 → 60 ms | ||
- 空間計算量:O(n) | ||
- DPの進捗保持 | ||
|
||
## Step 2. Alternatives | ||
|
||
- 標準ライブラリのstartswith | ||
- スライスを作らずに調べられるとのこと | ||
- https://github.com/tokuhirat/LeetCode/pull/39/files#diff-97b706d7e4155f93440639c77521868dfea1408527ec8ac173776bf438145440R28 | ||
- tailmatchで一文字ずつ比較しているように読める | ||
- https://github.com/python/cpython/blob/v3.6.1/Objects/unicodeobject.c#L13301-L13344 | ||
- BFSやDFSの実装も可能 | ||
- visitedを用いれば可能? | ||
- https://github.com/Mike0121/LeetCode/pull/52/files#diff-1c85c7c43d808a526e18efee43b20161b4b539852849addbec75200ea7a322baR39 | ||
- 自分の元の実装案にvisitedを加えて実装したら行けた | ||
- 関数内関数の実行回数は最大n回 | ||
- 再帰呼び出しを除けばO(k^2) | ||
- 結果的にO(n * k^2)で実行可能 | ||
- もっと速くしたければwordDict内のwordの長さのsetを作っておいてfor文の中で長さに一致する単語が1個以上あるか判定することもできる | ||
|
||
```python | ||
class Solution: | ||
def wordBreak(self, s: str, wordDict: List[str]) -> bool: | ||
max_word_length = len(max(wordDict, key=len)) | ||
wordDict = set(wordDict) | ||
tried = set() | ||
def isWordBreakableAfter(start: int) -> bool: | ||
if (len(s) - start) <= max_word_length and s[start:] in wordDict: | ||
return True | ||
word_length = min(max_word_length, len(s) - start) | ||
for i in range(word_length, 0, -1): | ||
if start + i in tried: | ||
continue | ||
if s[start:start + i] in wordDict: | ||
if isWordBreakableAfter(start + i): | ||
return True | ||
else: | ||
tried.add(start + i) | ||
return False | ||
return isWordBreakableAfter(0) | ||
``` | ||
|
||
- BFS・ループでも実装 | ||
- breakableToとtriedを保持しておくのもDPでTrue・Falseをつけていくのと情報的には同じ | ||
|
||
```python | ||
class Solution: | ||
def wordBreak(self, s: str, wordDict: List[str]) -> bool: | ||
words: set[str] = set() | ||
word_lengths: set[int] = set() | ||
for word in wordDict: | ||
words.add(word) | ||
word_lengths.add(len(word)) | ||
breakableTo: deque[int] = deque([0]) | ||
triedFrom: set[int] = set() | ||
string_length = len(s) | ||
while breakableTo: | ||
start = breakableTo.popleft() | ||
if start in triedFrom: | ||
continue | ||
triedFrom.add(start) | ||
for i in word_lengths: | ||
if s[start:start + i] in words: | ||
if start + i == string_length: | ||
return True | ||
breakableTo.append(start + i) | ||
return False | ||
``` | ||
|
||
- トップダウンの方が直感的という話、自分も共感した | ||
- 300文字の中から単語を見つけろって言われたら流石に途中で作業を交代して欲しい | ||
- https://github.com/Mike0121/LeetCode/pull/52/files#r1986286659 | ||
- Trie木でもできるという話 | ||
- https://github.com/tokuhirat/LeetCode/pull/39/files#diff-97b706d7e4155f93440639c77521868dfea1408527ec8ac173776bf438145440R92 | ||
- 今日はお腹いっぱいなので別日にやる | ||
|
||
## Step 3. Final Solution | ||
|
||
- DPでやるのがシンプルに書けそう | ||
- 引継ぎ手順書を書きながら漏れなく確認を進めていく感じ | ||
- s.startswithの前にスキップする分岐を入れることも考えたがコードが無駄に分かりにくくなるだけなのでやめた | ||
|
||
```python | ||
class Solution: | ||
def wordBreak(self, s: str, wordDict: List[str]) -> bool: | ||
string_length = len(s) | ||
breakableTo = [True] + [False] * string_length | ||
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. 変数名は snake case にするのが一般的だと思います。 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. breakableTo[i]とすれば英文的には分かりやすいのかなと思っていました 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. ただの一意見ですが、自分もlen(s)をそのまま使ってしまって良いと思いました。 |
||
for i in range(string_length): | ||
if not breakableTo[i]: | ||
continue | ||
for word in wordDict: | ||
if s.startswith(word, i): | ||
breakableTo[i + len(word)] = True | ||
return breakableTo[-1] | ||
``` |
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.
i より word_length の方が読みやすいと思いました。初め word_lengths の役割がわからなかったので、word_length_to_words = {文字数: list[単語]} のようにするとわかりやすくなるかもしれません。もしくはコメントで補足するのもありでしょうか。
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.
確かにそれありですね。ありがとうございます!