-
Notifications
You must be signed in to change notification settings - Fork 0
Word Ladder.md #18
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?
Word Ladder.md #18
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,243 @@ | ||
URL: https://leetcode.com/problems/word-ladder/description/ | ||
|
||
# Step 1 | ||
|
||
- 実装時間: 20分 | ||
|
||
TLEした回答 | ||
|
||
```python | ||
class Solution: | ||
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int: | ||
def is_adjacent_pair(str1, str2): | ||
distance = 0 | ||
for i in range(len(str1)): | ||
if str1[i] != str2[i]: | ||
distance += 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. distance が 2 になったところで return False すると、処理量が少し減ると思います。 |
||
return distance == 1 | ||
|
||
used_words = set() | ||
word_and_lengths = deque([(beginWord, 1)]) | ||
while word_and_lengths: | ||
word, length = word_and_lengths.popleft() | ||
if word == endWord: | ||
return length | ||
if word in used_words: | ||
continue | ||
used_words.add(word) | ||
for next_word in wordList: | ||
if is_adjacent_pair(word, next_word): | ||
word_and_lengths.append((next_word, length + 1)) | ||
return 0 | ||
``` | ||
|
||
- wordListの要素数を`n`, wordの長さ`l`をとして、 | ||
- 時間計算量: O(nl**2) | ||
- is_adjacent_pairでO(l) | ||
- search_adjacentsでO(n) | ||
- メインのループでO(l) | ||
- 空間計算量: O(n) | ||
|
||
|
||
```python | ||
class Solution: | ||
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int: | ||
def is_adjacent_pair(str1, str2): | ||
distance = 0 | ||
for i in range(len(str1)): | ||
if str1[i] != str2[i]: | ||
distance += 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. 2以上になったら False を返すのも一つです。 |
||
return distance == 1 | ||
|
||
def search_adjacents(word): | ||
adjacents = [] | ||
for next_word in wordList: | ||
if next_word in used_words: | ||
continue | ||
if is_adjacent_pair(word, next_word): | ||
adjacents.append(next_word) | ||
return adjacents | ||
|
||
used_words = set([beginWord]) | ||
word_and_lengths = deque([(beginWord, 1)]) | ||
while word_and_lengths: | ||
word, length = word_and_lengths.popleft() | ||
if word == endWord: | ||
return length | ||
for next_word in search_adjacents(word): | ||
if next_word in used_words: | ||
continue | ||
word_and_lengths.append((next_word, length + 1)) | ||
used_words.add(next_word) | ||
return 0 | ||
``` | ||
|
||
- いけるんじゃないかと思って、appendのタイミングとかを調整して対応した。 | ||
- PythonだとTLEということを事前に見積もれていなかった。 | ||
- https://github.com/fhiyo/leetcode/pull/22/files#r1641702410 | ||
|
||
# Step 2 | ||
|
||
- 参考にしたURL | ||
- https://github.com/fhiyo/leetcode/pull/22 | ||
- https://github.com/kazukiii/leetcode/pull/21 | ||
- https://github.com/Yoshiki-Iwasa/Arai60/pull/22 | ||
- https://github.com/TORUS0818/leetcode/pull/22 | ||
- https://github.com/Ryotaro25/leetcode_first60/pull/22 | ||
- https://github.com/seal-azarashi/leetcode/pull/19 | ||
- https://github.com/goto-untrapped/Arai60/pull/57 | ||
- https://github.com/nittoco/leetcode/pull/36 | ||
- https://github.com/hroc135/leetcode/pull/19 | ||
- https://github.com/Yusan1234/arai60/pull/1 | ||
- https://github.com/tarinaihitori/leetcode/pull/20 | ||
- https://github.com/colorbox/leetcode/pull/34 | ||
- https://github.com/hayashi-ay/leetcode/pull/42/files | ||
|
||
- used_wordsを管理するのではなくて、`unused_words = set(wordList)`を管理する。 | ||
- Step1では実装ではListに対してin演算をしていたので、遅い。setのinはO(1)。 | ||
|
||
```python | ||
class Solution: | ||
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int: | ||
def is_adjacent_pair(str1, str2): | ||
distance = 0 | ||
for i in range(len(str1)): | ||
if str1[i] != str2[i]: | ||
distance += 1 | ||
return distance == 1 | ||
|
||
def search_adjacents(word): | ||
adjacents = [] | ||
for next_word in wordList: | ||
if next_word not in unused_words: | ||
continue | ||
if is_adjacent_pair(word, next_word): | ||
adjacents.append(next_word) | ||
return adjacents | ||
|
||
unused_words = set(wordList) | ||
word_and_lengths = deque([(beginWord, 1)]) | ||
while word_and_lengths: | ||
word, length = word_and_lengths.popleft() | ||
if word == endWord: | ||
return length | ||
for next_word in search_adjacents(word): | ||
if next_word not in unused_words: | ||
continue | ||
word_and_lengths.append((next_word, length + 1)) | ||
unused_words.remove(next_word) | ||
return 0 | ||
``` | ||
|
||
- search_adjacentsがリストを作成するのは遅い? | ||
- リストを作って返すのではなく、yieldで都度返す。 | ||
- やって見たけどあんまりかわらなかった。 | ||
|
||
```python | ||
class Solution: | ||
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int: | ||
def is_adjacent_pair(str1, str2): | ||
distance = 0 | ||
for i in range(len(str1)): | ||
if str1[i] != str2[i]: | ||
distance += 1 | ||
return distance == 1 | ||
|
||
def search_adjacents(word): | ||
for next_word in wordList: | ||
if next_word not in unused_words: | ||
continue | ||
if is_adjacent_pair(word, next_word): | ||
yield next_word | ||
|
||
unused_words = set(wordList) | ||
word_and_lengths = deque([(beginWord, 1)]) | ||
while word_and_lengths: | ||
word, length = word_and_lengths.popleft() | ||
if word == endWord: | ||
return length | ||
for next_word in search_adjacents(word): | ||
if next_word not in unused_words: | ||
continue | ||
Comment on lines
+160
to
+161
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. L148~149かここの、どっちかは必要ないですかね |
||
word_and_lengths.append((next_word, length + 1)) | ||
unused_words.remove(next_word) | ||
return 0 | ||
``` | ||
|
||
- > 「wordList全体に対して隣接比較する」のではなく「1文字ずつ変えて、wordListに含まれるかチェック」の方が早い | ||
- 前者は`O(len(wordList)*len(word))` | ||
- 後者は`O(26*len(word))` | ||
- https://github.com/TORUS0818/leetcode/pull/22/files#r1666932153 | ||
|
||
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://discord.com/channels/1084280443945353267/1295357747545505833/1309222881330335816 |
||
```python | ||
class Solution: | ||
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int: | ||
def generate_adjacents(word): | ||
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. 個人的にはここは外側で定義して、generate_unused_adjacentsとして、引数はunused_wordsも含めます。(その方がインターフェーとしてわかりすい気がしました) |
||
for i in range(len(word)): | ||
for letter in ascii_lowercase: | ||
next_word = word[:i] + letter + word[i+1:] | ||
if next_word not in unused_words: | ||
continue | ||
yield next_word | ||
|
||
unused_words = set(wordList) | ||
word_and_lengths = deque([(beginWord, 1)]) | ||
while word_and_lengths: | ||
word, length = word_and_lengths.popleft() | ||
if word == endWord: | ||
return length | ||
for next_word in generate_adjacents(word): | ||
word_and_lengths.append((next_word, length + 1)) | ||
unused_words.remove(next_word) | ||
return 0 | ||
``` | ||
|
||
- `ascii_lowercase`を見てもピンとこなかったので、ドキュメントを読み返した。 | ||
- https://docs.python.org/3/library/string.html#string.ascii_lowercase | ||
|
||
- 文字列の結合を2回しているのが若干気になる | ||
- f-stringでやった方がいい? | ||
- 2回くらいなら許容な気がした。3回以上足し算があるなら、f-stringの方が読みやすそう | ||
|
||
- > で、そのおまけの話として、 | ||
hamming_distance(a, b) と hamming_distance(b, a) が同じ結果なのに別のキャッシュになるのを避けたい気持ちがあるので、@cache つけるのを補助関数にするなどを考えていました。 | ||
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. これ、たとえば、大小関係を入れようとすると、大小関係の確認に文字列を比べてしまい、hamming 距離の計算と遜色がない時間かかるので、id 化するなどちょっと工夫が必要でしょう。 |
||
- 今回の実装と関係ないけど、気にしたことがなかった。 | ||
|
||
# Step 3 | ||
|
||
- 実装時間: 4分 | ||
- wordListの要素数を`n`, wordの長さ`l`をとして、 | ||
- 時間計算量: O(ln) | ||
- generate_adjacentsでO(26 * l) | ||
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. 文字数だけループするのと、その中で新しい文字列を作成しているのでl^2ですかね? 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. ありがとうございます! lが見にくいも確かにです。ありがとうございます。 |
||
- メインのループでO(l) | ||
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. これはO(n)? |
||
- 空間計算量: O(n) | ||
|
||
```python | ||
class Solution: | ||
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int: | ||
unused_words = set(wordList) | ||
def generate_adjacents(word): | ||
for i in range(len(word)): | ||
for letter in ascii_lowercase: | ||
next_word = word[:i] + letter + word[i+1:] | ||
if next_word in unused_words: | ||
yield next_word | ||
|
||
word_and_lengths = deque([(beginWord, 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. 好みの問題かもですが、 |
||
while word_and_lengths: | ||
word, length = word_and_lengths.popleft() | ||
if word == endWord: | ||
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. この判定をnext_wordに対して行うことで、dequeに入れる前に判定できるので少しだけ高速化可能です。 |
||
return length | ||
for next_word in generate_adjacents(word): | ||
word_and_lengths.append((next_word, length + 1)) | ||
unused_words.remove(next_word) | ||
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. 個人的にはused_wordsに突っ込んでく方が好きですが、好みの問題かもしれません |
||
return 0 | ||
``` | ||
|
||
- 書いた感想 | ||
- 変数定義の場所について | ||
- `unused_words`は補助関数`generate_adjacents()`でも使うので、最初に定義したい。 | ||
- 関数外の変数を見に行くのちょっと嫌だけど、`ladderLength()`の関数内だし許容する。 | ||
- 一方で、`word_and_lengths`はループの中で使うものなので、whileループの近くで定義したい。 | ||
- 昨日と同様、覚えるというよりも考えて出力してる感じだった。今日も半日ぐらい間を空けたけど、気持ちよく書けた。 | ||
- `ascii_lowercase`だけ正式な名前が思い出せなくて、ドキュメントを見た。 |
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.
長さが違うものが来たらどうなるか考えておきましょう。
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.
ありがとうございます。
せっかくなので、面接だったらどういう選択肢を視野に入れて面接官とディスカッションするか考えてみました。
P.S. レーベンシュタイン距離は、正式な用語がわからずググりました。また、ハミング距離は”ハフマン距離”と間違えて覚えていました。。。
https://ja.wikipedia.org/wiki/%E3%83%AC%E3%83%BC%E3%83%99%E3%83%B3%E3%82%B7%E3%83%A5%E3%82%BF%E3%82%A4%E3%83%B3%E8%B7%9D%E9%9B%A2
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.
自分なら、問題としては長さが違うものが来てもOK。単にその単語が到達不能なだけで無視すれば良い。
is_adjacent_pair
の入力として長さが違う物が来たら、Falseを返すかなという感じです。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.
これは問題全体の要件が変わっているじゃんという気持ちです。まあそのレイヤーでの話をしているなら、良いですが、odaさんの意図とは違う気がします。