Skip to content

127. Word Ladder#19

Open
hroc135 wants to merge 6 commits intomainfrom
127WordLadder
Open

127. Word Ladder#19
hroc135 wants to merge 6 commits intomainfrom
127WordLadder

Conversation

@hroc135
Copy link
Copy Markdown
Owner

@hroc135 hroc135 commented Sep 22, 2024

- 「beginWordがwordListに必ずしも含まれているわけではない」という条件で少し躓いた
- ローカルのテストケースで無限ループに陥った。wordに対して訪問済みマークをつけるのを忘れていたことが原因
- 実装後、Wrong Answerが出た。パスの長さの管理が適切にできていなかったことが原因
- 時間計算量:O(n^2) (nはwordListの要素数。本当は、beginWordの長さをmとしてO(n^2 m)だが、m <= 10なので無視できる)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

定数でなく変数の場合はmは残しておいていい気がします。

}

checkedWords := make(map[string]struct{})
wordQueue := []graphNode{{word: beginWord, level: 1}}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wordに加えてlevelの情報が入っているので、変数名に反映すると良いのかなと思いました。

if w == endWord {
return first.level + 1
}
if _, ok := checkedWords[w]; ok {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

カンマokイディオムというのがあるのですね(独り言)

- https://cs.stackexchange.com/questions/93467/data-structure-or-algorithm-for-quickly-finding-differences-between-strings
- 速くなると思ったらLeetcodeの実行時間はstep1のBFSとほぼ同じ
- firstHalfToSecondHalfとsecondHalfToFirstHalfの初期化はO(n)でできており、BFSの無向グラフ初期化は0(n^2 / 2)なのでこの部分はかなり短縮できたはず
- 一方、isOneCharacterDifferent(str1, str2)とisOneCharacterDifferent(str2, str1)を計算せねばならず、これを必要としないstep1 BFSの方がこの部分が効率的になっている。GoにPythonのようなcacheデコレータがあれば簡単に改善できたはず
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mapでメモしておくとかではダメでしょうか。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

そうですね。Goで似た機能を実装しようとするとそれしかなさそうですね
step4で実装してみます!

level int
}

func ladderLength(beginWord string, endWord string, wordList []string) int {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

beginWord != endWord
という条件がついていましたが、この条件がないとこのコードでは何が置きますかね。
また、何が返ったら期待の動作でしょうか。
(こういうエッジケースは時々考えておきましょう。)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

このコードだと、beginWord -> xx -> ... -> yy -> endWordと遠回りしてたどり着けるならその時の語数が返り、遠回りしてたどり着けないなら0が返ります。
beginWord == endWordの時は直感的には1を返す実装にしたい気持ちになりますが、問題文の制約条件を守りたいなら返り値を(int, error)にして、return 0, errors.New("")とするかなと思います。この場合、ladderLength関数の頭でif beginWord == endWord { return ... }と書きます

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

はい。まず、これは正解がありません。
ユースケースを想定して、どうするのがよいのか考えて下さい。
また、いろいろな方法で書いていますが、それぞれについて入力の制限を守らない入力にたいして、同じ結果が出るか出ないかも考えてみましょう。
たとえば、Python だと infinity をいれると無限ループということがあったりします。


```Go
func ladderLength(beginWord string, endWord string, wordList []string) int {
transformationGraph := initTransformationGraph(beginWord, wordList)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

初期化するところで関数化するの、なるほどとなりました

Comment on lines +31 to +33
if isTransformable(wordList[i], wordList[j]) {
transformationGraph[wordList[i]] = append(transformationGraph[wordList[i]], wordList[j])
transformationGraph[wordList[j]] = append(transformationGraph[wordList[j]], wordList[i])
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こことL21-23の処理がほぼ同じなので、関数化してもいい気がしますが、3行をわざわざするか難しいですね

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

関数化すると、こんな感じですかね?動かしていないのでバグがあるかもしれませんが

func addEdgeToTransformationGraph(word1, word2 string, transformationGraph  map[string][]string) {
    transformationGraph[word1] = append(transformationGraph[word1], word2)
    transformationGraph[word2] = append(transformationGraph[word2], word1)
}

個人的には 2行 × 2箇所 の重複なのでわざわざ関数化しなくてもいいかな派です

for _, w1 := range wordsInSameLevel {
w1FirstHalf, w1SecondHalf := firstHalf(w1), secondHalf(w1)
for _, w2SecondHalf := range firstHalfToSecondHalf[w1FirstHalf] {
w2 := w1FirstHalf + w2SecondHalf
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

処理が複雑なので、変数名w2だと少し意図が掴みづらかったです。
candidate := w1FirstHalf + candidateSecondHalf
などはどうでしょう?
この辺のネストがやや深めの気もします。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

なるほど、candidateいいですね!

ネストについてはどうしようもなかったというのが正直なところなのですが、何かネストを下げる方法ってあったりしますか?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

遅くなりすみません🙏
うーん、L166-176, L177- 187をappend_next_level_words_from_first_half, from_second_halfとそれぞれ関数化するとかですかね。
ただendWordの時にreturn levelしなきゃいけないので、endWordかどうかも返さないといけなくなり、それはそれでややこしいですね

- firstHalfToSecondHalfとsecondHalfToFirstHalfの初期化はO(n)でできており、BFSの無向グラフ初期化は0(n^2 / 2)なのでこの部分はかなり短縮できたはず
- 一方、isOneCharacterDifferent(str1, str2)とisOneCharacterDifferent(str2, str1)を計算せねばならず、これを必要としないstep1 BFSの方がこの部分が効率的になっている。GoにPythonのようなcacheデコレータがあれば簡単に改善できたはず

```Go
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

個人的には、

  • firstHalfToSecondHalf, secondHalfToFirstHalfを尽くす処理
  • 次のlevelの単語を探す処理

あたりで関数化したいです。

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

そうですね、今自分でコードを読み返そうとしてすごい嫌な気分になりました、、
これは関数化必要ですね

}

func firstHalf(word string) string {
return word[:(len(word)+1)/2]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

逆にこれは関数化する必要があまりない気もしました(変数名を適切に付ければ伝わるような…)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここを関数化した理由としては、登場頻度が多いこともありますが、数式の入力はタイポしそうだなという不安があった、ということの方が大きいです。あとタイポして後からデバッグするときに見つけるのが大変、、
なので読み手の気持ちというより書き手の気持ちとして関数化したくなりました

return 0
}

func isTransformable(word1, word2 string) bool {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

遷移先を探す際、

  1. あらかじめ文字列とそのインデックスの map を作っておく。
  2. 遷移元の文字列を 1 文字ずつ a~z に置き換え、 1. の map の中に含まれているかを調べる。

としたほうが速いようです。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants