-
Notifications
You must be signed in to change notification settings - Fork 0
solved word ladder #45
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: arai60
Are you sure you want to change the base?
Conversation
| def is_connectable(s1: str, s2: str) -> bool: | ||
| count = 0 | ||
| for i in range(len(s1)): | ||
| if s1[i] != s2[i]: | ||
| count += 1 | ||
| return count == 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.
countが1を超えた段階でFalseを返すと、少しだけ早くできそうです。
| tail_word, sequence_length = queue.popleft() | ||
| for key in word_to_keys[tail_word]: | ||
| for word in key_to_words[key]: | ||
| if word not in seen: | ||
| if word == endWord: | ||
| return sequence_length + 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.
ここのネストがかなり深くなっていくので、tail_wordを引数として、次のwordを生成する関数があってもいいのではと感じました。以下みたいな感じでしょうか?
def generate_next_word(word: str, seen: set[str]) -> Iterator[str]:
for key in word_to_keys[word]:
for next_word in key_to_words[key]:
if next_word in seen:
continue
yield next_wordThere 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.
wordがendWordの時すぐreturnすればネストを一つ消せることに気づきましたが, それでもまだ長い気がするのでyieldを使うことにします。
while queue:
tail_word, sequence_length = queue.popleft()
for key in word_to_keys[tail_word]:
for word in key_to_words[key]:
if word == endWord:
return sequence_length + 1
if word not in seen:
queue.append((word, sequence_length + 1))
seen.add(word)| def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int: | ||
| wordList = set(wordList) | ||
| queue = deque([[beginWord, 1]]) | ||
| lower_characters = 'abcdefghijklmnopqrstuvwxyz' |
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.
string.ascii_lowercaseを使うのもありかと思います。字数はそれほど変わりませんが、タイプミスや入れ忘れは減ると思います。
| queue.append((word, sequence_length + 1)) | ||
| seen.add(word) | ||
| return 0 | ||
| # 書いてから思ったけど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.
僕もそう思います
| class Solution: | ||
| def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int: | ||
| for word in wordList: | ||
| if word == endWord: # ここ, if endWord not in wordListで効率化できるな |
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.
pythonのリストのin, not inについてはネイティブコードで実装されているので、速度的にも入力文字数という意味でもnot inとした方が良さそうです。
https://github.com/python/cpython/blob/main/Objects/listobject.c#L616
| key_to_words = defaultdict(list) | ||
| word_to_keys = defaultdict(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.
両方とも用意するのわかりやすくていいですね
| word_diff_count = 0 | ||
| for i in range(len(s1)): | ||
| if s1[i] != s2[i]: | ||
| word_diff_count += 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.
この問題で必要なのって、単語が隣接しているかどうかなので、diff_countについて2以上を数える必要がなくなり、そのようなロジックを組み込めるとそのぶんシンプルにできそうです。
| if tail_word == endWord: | ||
| return sequence_length | ||
| for word in available_words: | ||
| if calc_word_diff(tail_word, word) == 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.
ネストが深すぎるので、ここを反転させてearly continueみたいな感じでやれると読みやすくできそうです。
| @@ -0,0 +1,69 @@ | |||
| # 単純にBFSをしてtime limit overしたもの, そりゃそうだろうなという感じの時間計算量 | |||
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.
この解答だとqueue.append((word, next_available_words, sequence_length + 1))で使える単語も保持していて遠回りも含めた全部のルートを考えてしまっているので, 大雑把には最悪の場合n = wordList.lengthとした時n×(n-1)×(n-2)×...×1となってしまい, O(n!)かかります。Pythonは1秒10^6stepほどだった気がするので今回wordList.length <=5000であり, スターリング公式でn!≒n^(n+1/2) / e^nとして考えるとn=5000を代入して1831^5000 × 70 / 10^6≒(2000)^5000×70/10^6=7×10^16495 sec程度かかる気がします。(2^10≒10^3を用いて計算)
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.
あ、いやcalc_word_diffにword.length分かかるので今回word.length<=10なので7×10^16496 secでしょうか
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.
n×(n-1)×(n-2)×...×1となってしまい
ここの部分に違和感を感じました。単語 S1 → S2 → S3 → ... → Sn と遷移して endWord にたどり着く場合、 endWord に近づく遷移と遠ざかる遷移があります。遷移は n 回行われますので、多めに見積もって 2^n に比例した個数の状態が保持されます。また wordList のコピーを毎回作っていますので、 2^n * n になると思います。 n = 5000 を代入すると、 log_{10}2 = 0.3 として、 2^5000 * 5000 ≒ 10^1500 * 5000 = 5.0 * 10^1503 くらいになると思います。
単語 S1 → S2 → S3 → ... → Sn と遷移して endWord にたどり着くような入力を意図して作れるかは未検討です。
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.
はい、そのようにイメージしました。
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.
ちょっと実験してみました。
begin: hot, wordList: [lot, got, cot, cog], end: cogで少し樹形図書いてみたら9ルート出てきて確かに2^4のほうが4!よりは抑えられている気がしました。ありがとうございます。
| word_diff_count += 1 | ||
| return word_diff_count | ||
|
|
||
| word_graph = defaultdict(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.
グラフの中に入れる要素を単語本体ではなく wordList 内のインデックスとすることで、処理を少し軽くすることができるかもしれません。
| key = (word[:i], word[i + 1:]) | ||
| key_to_words[key].append(word) | ||
| word_to_keys[word].append(key) | ||
|
|
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.
私は初期化の部分で関数を分けるかもしれません。
問題
https://leetcode.com/problems/word-ladder/description/