From ad6b0da6862b3ee113cf40edb8e8e126478a3eff Mon Sep 17 00:00:00 2001 From: Masakuni Date: Sun, 25 Jan 2026 13:43:50 +0900 Subject: [PATCH 1/3] step1 --- problems/127.word-ladder/memo.md | 87 +++++++++++++++++++++++++++++ problems/127.word-ladder/step1-2.py | 49 ++++++++++++++++ problems/127.word-ladder/step1.py | 48 ++++++++++++++++ 3 files changed, 184 insertions(+) create mode 100644 problems/127.word-ladder/memo.md create mode 100644 problems/127.word-ladder/step1-2.py create mode 100644 problems/127.word-ladder/step1.py diff --git a/problems/127.word-ladder/memo.md b/problems/127.word-ladder/memo.md new file mode 100644 index 0000000..2f2501c --- /dev/null +++ b/problems/127.word-ladder/memo.md @@ -0,0 +1,87 @@ +## step1 +- 幅優先探索で行けるのではないかと思ったがどうか? +制約 +- wordListが5000 +- 単語は1~10文字 +- beginWord != endWord +- 単語リストの重複はなし +- beginWordはwordListの中になくてもいいが、endWordはないとだめ + +- 文字を1つ変えたものを探すのが結構大変そう + - 都度判定する場合は5000単語を確認する場合5000*10(何番目の単語が変わるか)で50000回 + - 遷移可能な単語を算出して(25**10)から単語リストから照らし合わせることもできるが単語の算出が大変すぎるのでありえなさそう +- 二次元配列or辞書(word_to_next)を持っておいて連結を管理する(無向グラフだと考えて) +- その後に幅優先探索するとできそう +- 下記でTLE +```py +# +# @lc app=leetcode id=127 lang=python3 +# +# [127] Word Ladder +# + +# @lc code=start +import collections + + +class Solution: + def ladderLength(self, beginWord: str, endWord: str, wordList: list[str]) -> int: + word_to_neighber = collections.defaultdict(set) + word_list_with_begin = wordList[:] + word_list_with_begin.append(beginWord) + + for index, word in enumerate(word_list_with_begin): + for neighber_candidate in word_list_with_begin[index + 1 :]: + if self._is_adjacent_pair(word, neighber_candidate): + word_to_neighber[word].add(neighber_candidate) + word_to_neighber[neighber_candidate].add(word) + + # endWordは必ずwordListに含まれなければ到達不可 + if not word_to_neighber[endWord]: + return 0 + + transformation_word_count = 1 + neighbers = set([beginWord]) + visited_words = set([beginWord]) + while neighbers: + print(neighbers) + if endWord in neighbers: + return transformation_word_count + next_neighbers = set() + for neighber in neighbers: + for next_neighber in word_to_neighber[neighber]: + if next_neighber not in visited_words: + next_neighbers.add(next_neighber) + visited_words.add(next_neighber) + + neighbers = next_neighbers + transformation_word_count += 1 + + return 0 + + @staticmethod + def _is_adjacent_pair(word1, word2): + assert len(word1) == len(word2) + diff_character_count = 0 + for i in range(len(word1)): + if word1[i] != word2[i]: + diff_character_count += 1 + + return diff_character_count == 1 +# @lc code=en +``` +- 時間計算量はword_to_neighberの作成でO(n^2) 1/2n(n+1) * 10 n=5000の時約5000*2500*10 = 1250万 +- 幅優先探索はN+Nなはずなので改善の余地としては遷移可能かを判定する箇所? +- 良い方法が浮かばなかったのでChatGPTに聞いた + - ★ ワイルドカード中間表現を使う + - ワイルドカードの表現をkey,valueのsetに元の文字列? + - patternを毎回作るのがめんどくさい気がする + - word_to_patternも用意しておくのは? + - 新しい辞書を作成するコストは掛かるが探索する際の時間が1/文字の数にできる?(step1-2) + - 実行時間はあまり変わらなかった + - patternsの中に次の単語が含まれる割合が小さすぎる&元の単語を毎回拾いvisited_wordsの条件でskipする回数が支配的? + +## step2 + +## step3 + diff --git a/problems/127.word-ladder/step1-2.py b/problems/127.word-ladder/step1-2.py new file mode 100644 index 0000000..62b9c1a --- /dev/null +++ b/problems/127.word-ladder/step1-2.py @@ -0,0 +1,49 @@ +# +# @lc app=leetcode id=127 lang=python3 +# +# [127] Word Ladder +# +import collections + + +class Solution: + def ladderLength(self, beginWord: str, endWord: str, wordList: list[str]) -> int: + # if endWord not in wordList: + # return 0 + + word_list_with_begin = wordList[:] + word_list_with_begin.append(beginWord) + + pattern_to_words = collections.defaultdict(set) + word_to_patterns = collections.defaultdict(set) + word_length = len(beginWord) + + for word in word_list_with_begin: + for i in range(word_length): + pattern = word[:i] + "*" + word[i + 1 :] + pattern_to_words[pattern].add(word) + word_to_patterns[word].add(pattern) + + transformation_word_count = 1 + neighbers = set([beginWord]) + visited_words = set([beginWord]) + + while neighbers: + if endWord in neighbers: + return transformation_word_count + + next_neighbers = set() + for word in neighbers: + for pattern in word_to_patterns[word]: + for neighber in pattern_to_words[pattern]: + if neighber not in visited_words: + visited_words.add(neighber) + next_neighbers.add(neighber) + + neighbers = next_neighbers + transformation_word_count += 1 + + return 0 + + +# @lc code=end diff --git a/problems/127.word-ladder/step1.py b/problems/127.word-ladder/step1.py new file mode 100644 index 0000000..67bb7bc --- /dev/null +++ b/problems/127.word-ladder/step1.py @@ -0,0 +1,48 @@ +# +# @lc app=leetcode id=127 lang=python3 +# +# [127] Word Ladder +# +import collections + + +class Solution: + def ladderLength(self, beginWord: str, endWord: str, wordList: list[str]) -> int: + # if endWord not in wordList: + # return 0 + + word_list_with_begin = wordList[:] + word_list_with_begin.append(beginWord) + + pattern_to_words = collections.defaultdict(set) + word_length = len(beginWord) + + for word in word_list_with_begin: + for i in range(word_length): + pattern = word[:i] + "*" + word[i + 1 :] + pattern_to_words[pattern].add(word) + + transformation_word_count = 1 + neighbers = set([beginWord]) + visited_words = set([beginWord]) + + while neighbers: + if endWord in neighbers: + return transformation_word_count + + next_neighbers = set() + for word in neighbers: + for i in range(word_length): + pattern = word[:i] + "*" + word[i + 1 :] + for next_word in pattern_to_words[pattern]: + if next_word not in visited_words: + visited_words.add(next_word) + next_neighbers.add(next_word) + + neighbers = next_neighbers + transformation_word_count += 1 + + return 0 + + +# @lc code=end From 8b80a285874ce0ec37b4d69158fa1fdc700b4630 Mon Sep 17 00:00:00 2001 From: Masakuni Date: Mon, 26 Jan 2026 21:30:36 +0900 Subject: [PATCH 2/3] step2 --- problems/127.word-ladder/memo.md | 17 +++++++++ problems/127.word-ladder/step2.py | 58 +++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 problems/127.word-ladder/step2.py diff --git a/problems/127.word-ladder/memo.md b/problems/127.word-ladder/memo.md index 2f2501c..a6f7fad 100644 --- a/problems/127.word-ladder/memo.md +++ b/problems/127.word-ladder/memo.md @@ -82,6 +82,23 @@ class Solution: - patternsの中に次の単語が含まれる割合が小さすぎる&元の単語を毎回拾いvisited_wordsの条件でskipする回数が支配的? ## step2 +- 他の人のコードを見てみる +- https://github.com/mamo3gr/arai60/pull/19/files + - 選択肢のまとめがすごくわかりやすかった + - `whileブロックの主役を明らかにする。` 意識したい + - `for c1, c2 in zip(word1, word2):` zipも使えるようにしたい + - 関数に細かく分かれてあって洗練されているように感じる +- https://github.com/Yuto729/LeetCode_arai60/pull/25 + - 先ほどのコードもそうだがデータを作成して操作する部分をクラスにするとスッキリ見える + - `Python の場合は、タプルも dict の Key にできるのでそれも一つかなと思います。`覚えておく + - `最短経路の単語列をすべて返す => バックトラック`最短経路の復元のやり方は思い出したい + - `wordListが非常に長い場合 => 1つのパターンあたりの要素数が平均して多くなるので, 小文字アルファベット26文字を総当りするほうが良くなるかもしれない` 確かに文字の長さが短く、wordListが非常に長い場合は有効かもしれない + - `この辺り、コードの大筋から外れて単に網羅的に1文字入れ替える作業が挟まるのが読みにくさを感じました。`僕にも当てはまっている +- https://github.com/yas-2023/leetcode_arai60/pull/20 + - `文字列を2箇所以上で結合するときは、二項演算子+よりf-stringの方がパフォーマンスが優れており、Google Style Guideでもf-stringによる結合が推奨されているそうです。`読みやすさもf-stringの方が優れていると思うので積極的に使っていきたい +- https://github.com/naoto-iwase/leetcode/pull/19/files + - `これはlistでもいいのではないかと思ったのですが、wordList内に重複がある場合(今回の問題では全てunique仮定ですが)に無駄な探索を減らすためということでしょうか?`自分もListで良い時にsetを使うことがあるが、コストの観点を見ていなかった。 + - `setの初期化は以下のようにも書いてもよさそうです` {initial}と書けるのを覚えておく ## step3 diff --git a/problems/127.word-ladder/step2.py b/problems/127.word-ladder/step2.py new file mode 100644 index 0000000..06b4be7 --- /dev/null +++ b/problems/127.word-ladder/step2.py @@ -0,0 +1,58 @@ +# +# @lc app=leetcode id=127 lang=python3 +# +# [127] Word Ladder +# + +# @lc code=start +import collections + + +class NeighberWord: + def __init__(self): + self.pattern_to_neighbers = collections.defaultdict(set) + + def add_word(self, word): + for pattern in self.word_to_pattern_iter(word): + self.pattern_to_neighbers[pattern].add(word) + + @staticmethod + def word_to_pattern_iter(word): + for i in range(len(word)): + yield word[:i], word[i + 1 :] + + def get_neighber_iter(self, word): + for pattern in self.word_to_pattern_iter(word): + for neighber in self.pattern_to_neighbers[pattern]: + yield neighber + + +class Solution: + def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int: + neighber_words = NeighberWord() + neighber_words.add_word(beginWord) + for word in wordList: + neighber_words.add_word(word) + + visited_words = {beginWord} + neighbers = {beginWord} + transformation_word_count = 1 + + while neighbers: + if endWord in neighbers: + return transformation_word_count + + next_neighbers = set() + for neighber in neighbers: + for next_neighber in neighber_words.get_neighber_iter(neighber): + if next_neighber in visited_words: + continue + visited_words.add(next_neighber) + next_neighbers.add(next_neighber) + neighbers = next_neighbers + transformation_word_count += 1 + + return 0 + + +# @lc code=end From e2951c6cef58d28b108244bfeb5745798dd78b48 Mon Sep 17 00:00:00 2001 From: Masakuni Date: Mon, 26 Jan 2026 21:43:29 +0900 Subject: [PATCH 3/3] step3 --- problems/127.word-ladder/memo.md | 3 ++ problems/127.word-ladder/step3.py | 58 +++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+) create mode 100644 problems/127.word-ladder/step3.py diff --git a/problems/127.word-ladder/memo.md b/problems/127.word-ladder/memo.md index a6f7fad..1d62c98 100644 --- a/problems/127.word-ladder/memo.md +++ b/problems/127.word-ladder/memo.md @@ -101,4 +101,7 @@ class Solution: - `setの初期化は以下のようにも書いてもよさそうです` {initial}と書けるのを覚えておく ## step3 +- 頭の中で整理できてきた気がする +- インデントのミスがあったりしたが見返して処理がイメージできるのでミスに早めに気づけた +- 1回書くのに10分~5分はかかっている diff --git a/problems/127.word-ladder/step3.py b/problems/127.word-ladder/step3.py new file mode 100644 index 0000000..13bc680 --- /dev/null +++ b/problems/127.word-ladder/step3.py @@ -0,0 +1,58 @@ +# +# @lc app=leetcode id=127 lang=python3 +# +# [127] Word Ladder +# + +# @lc code=start +import collections + + +class NeighberWord: + def __init__(self): + self.pattern_to_neighbers = collections.defaultdict(set) + + def add_word(self, word): + for pattern in self.word_to_pattern_iter(word): + self.pattern_to_neighbers[pattern].add(word) + + @staticmethod + def word_to_pattern_iter(word): + for i in range(len(word)): + yield word[:i], word[i + 1 :] + + def get_neighber_iter(self, word): + for pattern in self.word_to_pattern_iter(word): + for neighber in self.pattern_to_neighbers[pattern]: + yield neighber + + +class Solution: + def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int: + neighber_word = NeighberWord() + neighber_word.add_word(beginWord) + for word in wordList: + neighber_word.add_word(word) + + neighbers = {beginWord} + visited_words = {beginWord} + transformation_word_count = 1 + + while neighbers: + if endWord in neighbers: + return transformation_word_count + next_neighbers = set() + + for neighber in neighbers: + for next_neighber in neighber_word.get_neighber_iter(neighber): + if next_neighber in visited_words: + continue + next_neighbers.add(next_neighber) + visited_words.add(next_neighber) + neighbers = next_neighbers + transformation_word_count += 1 + + return 0 + + +# @lc code=end