From d208b593abf7bed915808e26b17802a118feec12 Mon Sep 17 00:00:00 2001 From: seal_azarashi Date: Wed, 4 Sep 2024 07:28:45 +0900 Subject: [PATCH 1/8] =?UTF-8?q?=E9=80=94=E4=B8=AD=E3=81=BE=E3=81=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arai60/Graph_BFS_DFS/word-ladder.md | 92 +++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 arai60/Graph_BFS_DFS/word-ladder.md diff --git a/arai60/Graph_BFS_DFS/word-ladder.md b/arai60/Graph_BFS_DFS/word-ladder.md new file mode 100644 index 0000000..22ee881 --- /dev/null +++ b/arai60/Graph_BFS_DFS/word-ladder.md @@ -0,0 +1,92 @@ +# 127. Word Ladder + +LeetCode URL: https://leetcode.com/problems/word-ladder/description/ + +この問題は Java で解いています。 +各解法において、メソッドが属するクラスとして `Solution` を定義していますが、これは Java の言語仕様に従い、コードを実行可能にするために必要なものです。このクラス自体には特定の意味はなく、単にメソッドを組織化し、実行可能にするためのものです。 + +## Step 1 + +なんとなくいける気がしたので2時間だけ頑張ってみました。残念ながらあるテストケースで TLE となりました。 +アルゴリズムだけでなく変数の命名等についても課題が多いままですが、一応最初の解答ということで記載しておきます。 + +```java +// 時間計算量: ? +// 空間計算量: ? +class Solution { + public int ladderLength(String beginWord, String endWord, List wordList) { + int ladderLength = 0; + for (String word : wordList) { + if (word.equals(endWord)) { + ladderLength = getShortestTransformationSequence(beginWord, word, wordList); + } + } + return ladderLength; + } + + private int getShortestTransformationSequence(String beginWord, String endWord, List wordList) { + if (isTransformable(beginWord, endWord)) { + return 2; + } + List newWordList = createNewWordList(endWord, wordList); + if (newWordList.isEmpty()) { + return 0; + } + + int shortestTransformationSequence = 0; + for (String word : newWordList) { + if (!isTransformable(word, endWord)) { + continue; + } + int transformationSequence = getShortestTransformationSequence(beginWord, word, newWordList); + if (transformationSequence > 0 && transformationSequence < shortestTransformationSequence || shortestTransformationSequence == 0) { + shortestTransformationSequence = transformationSequence; + } + } + + if (shortestTransformationSequence == 0) { + return 0; + } + return shortestTransformationSequence + 1; + } + + private boolean isTransformable(String xWord, String yWord) { + int difference = 0; + char[] xChars = xWord.toCharArray(); + char[] yChars = yWord.toCharArray(); + for (int i = 0; i < xChars.length; i++) { + if (xChars[i] != yChars[i]) { + difference++; + } + } + return difference <= 1; + } + + private List createNewWordList(String endWord, List wordList) { + String targetWord = ""; + for (String word : wordList) { + if (word.equals(endWord)) { + targetWord = word; + } + } + if (targetWord.equals("")) { + return new ArrayList<>(); + } + List transformableWordList = new ArrayList<>(wordList); + transformableWordList.remove(targetWord); + return transformableWordList; + } +} +``` + +以下のようなことを考えて実装していました: + +- endWord から beginWord の adjacent pair に到れるかどうかを確認し、到った中で最短 transformation sequence を返す実装にすれば良さそう +- キャッシュしてあげないと TLE になりそうだが、その実装は一旦処理が完成してからにした方が順序としては望ましいかな +- 可読性を考えると再帰関数にするのが良い気がする + - 再帰の深さは wordList.length の最大値 5000 が上限となるはずなので、 Java なら恐らくスタックオーバーフローにはならないだろう +- 必要な関数は色々考えて次の3つになった: + - 再帰関数 getShortestTransformationSequence() + - 2つの word が adjacent pair であるかどうかを判定する isTransformable() + - 対象の word を除いたリストを作成する createNewWordList() +- ... From 3d2094595a20543f897849e05698e2c981e87e8b Mon Sep 17 00:00:00 2001 From: seal_azarashi Date: Wed, 4 Sep 2024 07:51:52 +0900 Subject: [PATCH 2/8] =?UTF-8?q?step=201=20=E9=80=94=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- arai60/Graph_BFS_DFS/word-ladder.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/arai60/Graph_BFS_DFS/word-ladder.md b/arai60/Graph_BFS_DFS/word-ladder.md index 22ee881..6105b08 100644 --- a/arai60/Graph_BFS_DFS/word-ladder.md +++ b/arai60/Graph_BFS_DFS/word-ladder.md @@ -11,8 +11,8 @@ LeetCode URL: https://leetcode.com/problems/word-ladder/description/ アルゴリズムだけでなく変数の命名等についても課題が多いままですが、一応最初の解答ということで記載しておきます。 ```java -// 時間計算量: ? -// 空間計算量: ? +// 時間計算量: O(n^2) +// 空間計算量: O(n^2) class Solution { public int ladderLength(String beginWord, String endWord, List wordList) { int ladderLength = 0; @@ -84,9 +84,10 @@ class Solution { - endWord から beginWord の adjacent pair に到れるかどうかを確認し、到った中で最短 transformation sequence を返す実装にすれば良さそう - キャッシュしてあげないと TLE になりそうだが、その実装は一旦処理が完成してからにした方が順序としては望ましいかな - 可読性を考えると再帰関数にするのが良い気がする - - 再帰の深さは wordList.length の最大値 5000 が上限となるはずなので、 Java なら恐らくスタックオーバーフローにはならないだろう -- 必要な関数は色々考えて次の3つになった: + - 再帰の深さは wordList の最大要素数 5000 が上限となるはずなので、 Java なら恐らくスタックオーバーフローにはならないだろう +- 必要な関数は次の3つになるだろうか: - 再帰関数 getShortestTransformationSequence() - 2つの word が adjacent pair であるかどうかを判定する isTransformable() - 対象の word を除いたリストを作成する createNewWordList() -- ... +- いくつかケースが通るようになったがやはり要素数が多いと TLE になった +- ここで確保してた2時間が経ったので終了 From 7e6ed3bb5dfa7a55a55c8edc8a9c5af677cd4320 Mon Sep 17 00:00:00 2001 From: seal_azarashi Date: Wed, 4 Sep 2024 08:38:35 +0900 Subject: [PATCH 3/8] finish step 1 --- arai60/Graph_BFS_DFS/word-ladder.md | 64 ++++++++++++++++++++++++++++- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/arai60/Graph_BFS_DFS/word-ladder.md b/arai60/Graph_BFS_DFS/word-ladder.md index 6105b08..7a64c0b 100644 --- a/arai60/Graph_BFS_DFS/word-ladder.md +++ b/arai60/Graph_BFS_DFS/word-ladder.md @@ -7,12 +7,72 @@ LeetCode URL: https://leetcode.com/problems/word-ladder/description/ ## Step 1 -なんとなくいける気がしたので2時間だけ頑張ってみました。残念ながらあるテストケースで TLE となりました。 +### 答えを見て解いたときの実装 + +```java +// 時間計算量: O(n^2) +// 空間計算量: O(n) +class Solution { + private static final char MATCHES_SINGLE_CHARACTER = '?'; + + public int ladderLength(String beginWord, String endWord, List wordList) { + wordList.add(beginWord); + Map> globWordsMap = new HashMap<>(); + for (String word : wordList) { + for (int i = 0; i < word.length(); i++) { + StringBuilder wordStringBuilder = new StringBuilder(word); + wordStringBuilder.setCharAt(i, MATCHES_SINGLE_CHARACTER); + String glob = wordStringBuilder.toString(); + List list = globWordsMap.getOrDefault(glob, new ArrayList<>()); + list.add(word); + globWordsMap.put(glob, list); + } + } + Queue wordQueue = new LinkedList<>(); + wordQueue.offer(beginWord); + Set visitedWords = new HashSet<>(); + visitedWords.add(beginWord); + + int step = 1; + while (!wordQueue.isEmpty()) { + step++; + + int wordQueueSizeSnapShot = wordQueue.size(); + for (int wordQueueIndex = 0; wordQueueIndex < wordQueueSizeSnapShot; wordQueueIndex++) { + String word = wordQueue.poll(); + for (int i = 0; i < word.length(); i++) { + StringBuilder wordStringBuilder = new StringBuilder(word); + wordStringBuilder.setCharAt(i, MATCHES_SINGLE_CHARACTER); + String glob = wordStringBuilder.toString(); + for (String adjacentWord : globWordsMap.get(glob)) { + if (adjacentWord.equals(endWord)) { + return step; + } + if (visitedWords.contains(adjacentWord)) { + continue; + } + + wordQueue.offer(adjacentWord); + visitedWords.add(adjacentWord); + } + } + } + } + + return 0; + } +} +``` + +### 答えを見ずに解こうとした記録 + +なんとなくいける気がしたので、答えを見る前に2時間だけ頑張ってみました。残念ながらあるテストケースで TLE となりました。 アルゴリズムだけでなく変数の命名等についても課題が多いままですが、一応最初の解答ということで記載しておきます。 ```java +// 使った時間: 2時間 // 時間計算量: O(n^2) -// 空間計算量: O(n^2) +// 空間計算量: O(n) class Solution { public int ladderLength(String beginWord, String endWord, List wordList) { int ladderLength = 0; From 3b5f7276608e77a73229092be99fdf08f623a649 Mon Sep 17 00:00:00 2001 From: seal_azarashi Date: Mon, 9 Sep 2024 07:33:04 +0900 Subject: [PATCH 4/8] Step 2 --- arai60/Graph_BFS_DFS/word-ladder.md | 143 ++++++++++++++++++++++++++-- 1 file changed, 133 insertions(+), 10 deletions(-) diff --git a/arai60/Graph_BFS_DFS/word-ladder.md b/arai60/Graph_BFS_DFS/word-ladder.md index 7a64c0b..430fdb2 100644 --- a/arai60/Graph_BFS_DFS/word-ladder.md +++ b/arai60/Graph_BFS_DFS/word-ladder.md @@ -7,13 +7,13 @@ LeetCode URL: https://leetcode.com/problems/word-ladder/description/ ## Step 1 -### 答えを見て解いたときの実装 +### 答えを見て解いたときの実装 (パターンと文字列の対応を用いた実装) ```java -// 時間計算量: O(n^2) -// 空間計算量: O(n) +// 時間計算量: O(N^2) +// 空間計算量: O(N) class Solution { - private static final char MATCHES_SINGLE_CHARACTER = '?'; + private static final char MATCHES_ANY_SINGLE_CHARACTER = '?'; public int ladderLength(String beginWord, String endWord, List wordList) { wordList.add(beginWord); @@ -21,7 +21,7 @@ class Solution { for (String word : wordList) { for (int i = 0; i < word.length(); i++) { StringBuilder wordStringBuilder = new StringBuilder(word); - wordStringBuilder.setCharAt(i, MATCHES_SINGLE_CHARACTER); + wordStringBuilder.setCharAt(i, MATCHES_ANY_SINGLE_CHARACTER); String glob = wordStringBuilder.toString(); List list = globWordsMap.getOrDefault(glob, new ArrayList<>()); list.add(word); @@ -42,7 +42,7 @@ class Solution { String word = wordQueue.poll(); for (int i = 0; i < word.length(); i++) { StringBuilder wordStringBuilder = new StringBuilder(word); - wordStringBuilder.setCharAt(i, MATCHES_SINGLE_CHARACTER); + wordStringBuilder.setCharAt(i, MATCHES_ANY_SINGLE_CHARACTER); String glob = wordStringBuilder.toString(); for (String adjacentWord : globWordsMap.get(glob)) { if (adjacentWord.equals(endWord)) { @@ -66,13 +66,13 @@ class Solution { ### 答えを見ずに解こうとした記録 -なんとなくいける気がしたので、答えを見る前に2時間だけ頑張ってみました。残念ながらあるテストケースで TLE となりました。 +なんとなくいける気がしたので、答えを見る前に2時間だけ頑張ってみました。残念ながらあるテストケースで TLE となりました。答えを見てから振り返ると、これじゃあいかんなという印象です。 アルゴリズムだけでなく変数の命名等についても課題が多いままですが、一応最初の解答ということで記載しておきます。 ```java // 使った時間: 2時間 -// 時間計算量: O(n^2) -// 空間計算量: O(n) +// 時間計算量: O(N^3) +// 空間計算量: O(N) class Solution { public int ladderLength(String beginWord, String endWord, List wordList) { int ladderLength = 0; @@ -139,7 +139,13 @@ class Solution { } ``` -以下のようなことを考えて実装していました: +今振り返ると次のような感想が思い浮かびます: + +- そもそも DFS アプローチで行こうとしているのが筋がわるい。最短経路探索なのでまず BFS が思い浮かぶべき。 + - [fhiyo さんの「エッジの重みがすべて1のグラフ上の最短距離を求めるのだからBFSがまず候補に入るはず。」という思考ログ](https://github.com/fhiyo/leetcode/pull/22/files)を見て、自分もこれがまず頭に浮かぶようになりたいなと思った +- キャッシュしないと厳しいとわかっていたのだから最初から実装に組み込むべきだった + +実装当時は以下のようなことを考えて実装していました: - endWord から beginWord の adjacent pair に到れるかどうかを確認し、到った中で最短 transformation sequence を返す実装にすれば良さそう - キャッシュしてあげないと TLE になりそうだが、その実装は一旦処理が完成してからにした方が順序としては望ましいかな @@ -151,3 +157,120 @@ class Solution { - 対象の word を除いたリストを作成する createNewWordList() - いくつかケースが通るようになったがやはり要素数が多いと TLE になった - ここで確保してた2時間が経ったので終了 + +## Step 2 + +### パターンと文字列の対応を用いた実装 (Step 1 の解法の修正) + +```java +// 時間計算量: O(N^2) +// 空間計算量: O(N) +class Solution { + private static final int NO_SEQUENCE_FOUND = 0; + + public int ladderLength(String beginWord, String endWord, List wordList) { + Map> globWordsMap = new HashMap<>(); + // 引数に与えられた値を書き換えることを良しとしています + wordList.add(beginWord); + for (String word : wordList) { + for (int i = 0; i < word.length(); i++) { + String globPattern = createGlobPattern(i, word); + List words = globWordsMap.getOrDefault(globPattern, new ArrayList<>()); + words.add(word); + globWordsMap.put(globPattern, words); + } + } + + Queue wordQueue = new ArrayDeque<>(); + wordQueue.offer(beginWord); + Set visitedWords = new HashSet<>(); + visitedWords.add(beginWord); + + int distance = 1; + while (!wordQueue.isEmpty()) { + distance++; + int wordQueueSizeSnapshot = wordQueue.size(); + for (int i = 0; i < wordQueueSizeSnapshot; i++) { + String word = wordQueue.poll(); + for (int j = 0; j < word.length(); j++) { + String globPattern = createGlobPattern(j, word); + for (String adjacentWord : globWordsMap.get(globPattern)) { + if (adjacentWord.equals(endWord)) { + return distance; + } + if (visitedWords.contains(adjacentWord)) { + continue; + } + wordQueue.offer(adjacentWord); + visitedWords.add(adjacentWord); + } + } + } + } + + return NO_SEQUENCE_FOUND; + } + + private String createGlobPattern(int index, String word) { + StringBuilder patternBuilder = new StringBuilder(word); + patternBuilder.setCharAt(index, '?'); + return patternBuilder.toString(); + } +} +``` + +- Levenshtein distance, Edit distance, Hamming distance についての知識があれば `step` よりも `distance` の方が役割を理解しやすいなと思ったので変数名を修正 ([こちらのコメント](https://github.com/Ryotaro25/leetcode_first60/pull/22#issuecomment-2255580125)とその引用から知りました) +- クラスの外から渡される引数 wordList を書き換える副作用があっていいのかは議論の余地ありなので、一応その点コメント + +### Deque に単語と編集距離をもたせる BFS アプローチ + +[torus さんの解答](https://github.com/TORUS0818/leetcode/pull/22/files?short_path=d25ca00#diff-d25ca007ef80e5e40d07985c432c899c349a715552d7ec202509fbf470586658)を参考に実装してみた。 + +```java +// 時間計算量: O(N^2) +// 空間計算量: O(N) +class Solution { + private record WordDistance(String word, int distance) {}; + private static final int NO_SEQUENCE_EXISTS = 0; + + public int ladderLength(String beginWord, String endWord, List wordList) { + Deque wordDistances = new ArrayDeque<>(); + wordDistances.addLast(new WordDistance(beginWord, 1)); + while (!wordDistances.isEmpty()) { + WordDistance wordDistance = wordDistances.removeFirst(); + if (wordDistance.word.equals(endWord)) { + return wordDistance.distance; + } + Set addedWords = new HashSet<>(); + for (String targetWord : wordList) { + if (!isTransformable(targetWord, wordDistance.word)) { + continue; + } + addedWords.add(targetWord); + wordDistances.addLast(new WordDistance(targetWord, wordDistance.distance + 1)); + } + wordList.removeAll(addedWords); + } + return NO_SEQUENCE_EXISTS; + } + + private boolean isTransformable(String xWord, String yWord) { + char[] xChars = xWord.toCharArray(); + char[] yChars = yWord.toCharArray(); + int difference = 0; + // 両方の引数の文字数が等しいことを前提とした実装になります + for (int i = 0; i < xChars.length; i++) { + if (xChars[i] != yChars[i]) { + difference++; + } + if (difference > 1) { + return false; + } + } + return true; + } +} +``` + +- 自分も自然に思いつきそうだなという印象を持った +- パターンと文字列の対応を用いた実装と比べ実行時間は1桁多かった From 1da3dd5605480ea8c660f584b9dd9ce5a4c81d3a Mon Sep 17 00:00:00 2001 From: seal_azarashi Date: Mon, 9 Sep 2024 08:38:45 +0900 Subject: [PATCH 5/8] step 3 --- arai60/Graph_BFS_DFS/word-ladder.md | 61 +++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/arai60/Graph_BFS_DFS/word-ladder.md b/arai60/Graph_BFS_DFS/word-ladder.md index 430fdb2..c83eab2 100644 --- a/arai60/Graph_BFS_DFS/word-ladder.md +++ b/arai60/Graph_BFS_DFS/word-ladder.md @@ -274,3 +274,64 @@ class Solution { - 自分も自然に思いつきそうだなという印象を持った - パターンと文字列の対応を用いた実装と比べ実行時間は1桁多かった + +## Step 3 + +パターンと文字列の対応を用いた実装にしました。 + +```java +// 解いた時間: 20分ぐらい +// 時間計算量: O(N^2) +// 空間計算量: O(N) +class Solution { + public int ladderLength(String beginWord, String endWord, List wordList) { + wordList.add(beginWord); + Map> adjacencyMap = new HashMap<>(); + for (String word : wordList) { + for (int i = 0; i < word.length(); i++) { + String globPattern = createGlobPattern(i, word); + List list = adjacencyMap.getOrDefault(globPattern, new ArrayList<>()); + list.add(word); + adjacencyMap.put(globPattern, list); + } + } + + Deque wordQueue = new ArrayDeque<>(); + wordQueue.addLast(beginWord); + Set visitedWords = new HashSet<>(); + visitedWords.add(beginWord); + + int distance = 1; + while(!wordQueue.isEmpty()) { + distance++; + int wordQueueSizeSnapshot = wordQueue.size(); + for (int wordQueueIndex = 0; wordQueueIndex < wordQueueSizeSnapshot; wordQueueIndex++) { + String word = wordQueue.removeFirst(); + for (int i = 0; i < word.length(); i++) { + String globPattern = createGlobPattern(i, word); + for (String adjacentWord : adjacencyMap.get(globPattern)) { + if (adjacentWord.equals(endWord)) { + return distance; + } + if (visitedWords.contains(adjacentWord)) { + continue; + } + wordQueue.addLast(adjacentWord); + visitedWords.add(adjacentWord); + } + } + } + } + + return 0; + } + + private String createGlobPattern(int index, String word) { + StringBuilder globPatternBuilder = new StringBuilder(word); + globPatternBuilder.setCharAt(index, '?'); + return globPatternBuilder.toString(); + } +} +``` + +- 書く量が多く大変だったからか、 0 を定数化したりコメントを書くのが抜けていた From 5f4b062756138f4c4477762bc47af0cfa2a151bb Mon Sep 17 00:00:00 2001 From: seal_azarashi Date: Mon, 30 Sep 2024 07:27:00 +0900 Subject: [PATCH 6/8] fix complexity calculation --- arai60/Graph_BFS_DFS/word-ladder.md | 41 ++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/arai60/Graph_BFS_DFS/word-ladder.md b/arai60/Graph_BFS_DFS/word-ladder.md index c83eab2..51c6fe1 100644 --- a/arai60/Graph_BFS_DFS/word-ladder.md +++ b/arai60/Graph_BFS_DFS/word-ladder.md @@ -10,8 +10,17 @@ LeetCode URL: https://leetcode.com/problems/word-ladder/description/ ### 答えを見て解いたときの実装 (パターンと文字列の対応を用いた実装) ```java -// 時間計算量: O(N^2) -// 空間計算量: O(N) +/** + * 時間計算量: O(n * m^2): + * - O(n): wordList のクローン (n は wordList の要素数) + * - O(n * m^2): globPatternToWords の生成 (n は wordListClone の要素数、 m は要素の文字数) + * - O(n * m^2): wordQueue の要素の走査 (n は wordQueue の最大要素数、 m は要素の文字数) + * 空間計算量: O(n * m) + * - O(n): wordListClone + * - O(n * m): globPatternToWords (n は Map の要素数、 m は value の要素数) + * - O(n): wordQueue + * - O(n): visitedWords + */ class Solution { private static final char MATCHES_ANY_SINGLE_CHARACTER = '?'; @@ -163,8 +172,17 @@ class Solution { ### パターンと文字列の対応を用いた実装 (Step 1 の解法の修正) ```java -// 時間計算量: O(N^2) -// 空間計算量: O(N) +/** + * 時間計算量: O(n * m^2): + * - O(n): wordList のクローン (n は wordList の要素数) + * - O(n * m^2): globPatternToWords の生成 (n は wordListClone の要素数、 m は要素の文字数) + * - O(n * m^2): wordQueue の要素の走査 (n は wordQueue の最大要素数、 m は要素の文字数) + * 空間計算量: O(n * m) + * - O(n): wordListClone + * - O(n * m): globPatternToWords (n は Map の要素数、 m は value の要素数) + * - O(n): wordQueue + * - O(n): visitedWords + */ class Solution { private static final int NO_SEQUENCE_FOUND = 0; @@ -280,9 +298,18 @@ class Solution { パターンと文字列の対応を用いた実装にしました。 ```java -// 解いた時間: 20分ぐらい -// 時間計算量: O(N^2) -// 空間計算量: O(N) +/** + * 解いた時間: 20分ぐらい + * 時間計算量: O(n * m^2): + * - O(n): wordList のクローン (n は wordList の要素数) + * - O(n * m^2): globPatternToWords の生成 (n は wordListClone の要素数、 m は要素の文字数) + * - O(n * m^2): wordQueue の要素の走査 (n は wordQueue の最大要素数、 m は要素の文字数) + * 空間計算量: O(n * m) + * - O(n): wordListClone + * - O(n * m): globPatternToWords (n は Map の要素数、 m は value の要素数) + * - O(n): wordQueue + * - O(n): visitedWords + */ class Solution { public int ladderLength(String beginWord, String endWord, List wordList) { wordList.add(beginWord); From 98309c5c8c0a3a1c4e2b18448f7f3aab35aa68f1 Mon Sep 17 00:00:00 2001 From: seal_azarashi Date: Mon, 30 Sep 2024 07:50:21 +0900 Subject: [PATCH 7/8] step 4 --- arai60/Graph_BFS_DFS/word-ladder.md | 98 +++++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 6 deletions(-) diff --git a/arai60/Graph_BFS_DFS/word-ladder.md b/arai60/Graph_BFS_DFS/word-ladder.md index 51c6fe1..f2b79c9 100644 --- a/arai60/Graph_BFS_DFS/word-ladder.md +++ b/arai60/Graph_BFS_DFS/word-ladder.md @@ -13,11 +13,11 @@ LeetCode URL: https://leetcode.com/problems/word-ladder/description/ /** * 時間計算量: O(n * m^2): * - O(n): wordList のクローン (n は wordList の要素数) - * - O(n * m^2): globPatternToWords の生成 (n は wordListClone の要素数、 m は要素の文字数) + * - O(n * m^2): Map の生成 (n は wordListClone の要素数、 m は要素の文字数) * - O(n * m^2): wordQueue の要素の走査 (n は wordQueue の最大要素数、 m は要素の文字数) * 空間計算量: O(n * m) * - O(n): wordListClone - * - O(n * m): globPatternToWords (n は Map の要素数、 m は value の要素数) + * - O(n * m): Map (n は Map の要素数、 m は value の要素数) * - O(n): wordQueue * - O(n): visitedWords */ @@ -175,11 +175,11 @@ class Solution { /** * 時間計算量: O(n * m^2): * - O(n): wordList のクローン (n は wordList の要素数) - * - O(n * m^2): globPatternToWords の生成 (n は wordListClone の要素数、 m は要素の文字数) + * - O(n * m^2): Map の生成 (n は wordListClone の要素数、 m は要素の文字数) * - O(n * m^2): wordQueue の要素の走査 (n は wordQueue の最大要素数、 m は要素の文字数) * 空間計算量: O(n * m) * - O(n): wordListClone - * - O(n * m): globPatternToWords (n は Map の要素数、 m は value の要素数) + * - O(n * m): Map (n は Map の要素数、 m は value の要素数) * - O(n): wordQueue * - O(n): visitedWords */ @@ -302,11 +302,11 @@ class Solution { * 解いた時間: 20分ぐらい * 時間計算量: O(n * m^2): * - O(n): wordList のクローン (n は wordList の要素数) - * - O(n * m^2): globPatternToWords の生成 (n は wordListClone の要素数、 m は要素の文字数) + * - O(n * m^2): Map の生成 (n は wordListClone の要素数、 m は要素の文字数) * - O(n * m^2): wordQueue の要素の走査 (n は wordQueue の最大要素数、 m は要素の文字数) * 空間計算量: O(n * m) * - O(n): wordListClone - * - O(n * m): globPatternToWords (n は Map の要素数、 m は value の要素数) + * - O(n * m): Map (n は Map の要素数、 m は value の要素数) * - O(n): wordQueue * - O(n): visitedWords */ @@ -362,3 +362,89 @@ class Solution { ``` - 書く量が多く大変だったからか、 0 を定数化したりコメントを書くのが抜けていた + +## Step 4 + +以下の指摘に対応: + +- パターン文字列を一般的な String 値に含まれない null 文字に修正: https://github.com/seal-azarashi/leetcode/pull/19#discussion_r1750175006 +- 返り値の変数名を numberOfWordsInSequence に修正: https://github.com/seal-azarashi/leetcode/pull/19#discussion_r1750182576 +- 引数に破壊的な変更が行われないようにクローンオブジェクトを利用: https://github.com/seal-azarashi/leetcode/pull/19#discussion_r1753902722 +- ネストが深い部分を関数化: https://github.com/seal-azarashi/leetcode/pull/19#discussion_r1777273058 +- ループごとにキューを生成: https://github.com/seal-azarashi/leetcode/pull/19#discussion_r1777280979 + +```java +/** + * 時間計算量: O(n * m^2): + * - O(n): wordList のクローン (n は wordList の要素数) + * - O(n * m^2): Map の生成 (n は wordListClone の要素数、 m は要素の文字数) + * - O(n * m^2): wordQueue の要素の走査 (n は wordQueue の最大要素数、 m は要素の文字数) + * 空間計算量: O(n * m) + * - O(n): wordListClone + * - O(n * m): Map (n は Map の要素数、 m は value の要素数) + * - O(n): wordQueue + * - O(n): visitedWords + */ +class Solution { + public int ladderLength(String beginWord, String endWord, List wordList) { + List wordListClone = new ArrayList(wordList); + wordListClone.add(beginWord); + Map> globPatternToWords = new HashMap(); + for (String word : wordListClone) { + for (int i = 0; i < word.length(); i++) { + String globPattern = createGlobPattern(i, word); + List words = globPatternToWords.getOrDefault(globPattern, new ArrayList()); + words.add(word); + globPatternToWords.put(globPattern, words); + } + } + + Deque wordQueue = new ArrayDeque(); + wordQueue.offer(beginWord); + Set visitedWords = new HashSet(); + int numberOfWordsInSequence = 1; + while (!wordQueue.isEmpty()) { + numberOfWordsInSequence++; + Deque nextWordQueue = new ArrayDeque(); + for (String word : wordQueue) { + boolean isSequenceFound = evaluateWord(word, endWord, globPatternToWords, nextWordQueue, visitedWords); + if (isSequenceFound) { + return numberOfWordsInSequence; + } + } + wordQueue = nextWordQueue; + } + return 0; + } + + private String createGlobPattern(int replaceCharIndex, String word) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < word.length(); i++) { + if (i == replaceCharIndex) { + sb.append('\0'); + } else { + sb.append(word.charAt(i)); + } + } + return sb.toString(); + } + + private boolean evaluateWord(String word, String endWord, Map> globPatternToWords, Deque nextWordQueue, Set visitedWords) { + for (int i = 0; i < word.length(); i++) { + String globPattern = createGlobPattern(i, word); + List nextWords = globPatternToWords.getOrDefault(globPattern, new ArrayList()); + for (String nextWord : nextWords) { + if (nextWord.equals(endWord)) { + return true; + } + if (visitedWords.contains(nextWord)) { + continue; + } + nextWordQueue.offer(nextWord); + visitedWords.add(nextWord); + } + } + return false; + } +} +``` From 5dd1f22d9076613b4e0f3477ce4c62514189bc56 Mon Sep 17 00:00:00 2001 From: seal_azarashi Date: Mon, 30 Sep 2024 07:52:05 +0900 Subject: [PATCH 8/8] fix comments --- arai60/Graph_BFS_DFS/word-ladder.md | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/arai60/Graph_BFS_DFS/word-ladder.md b/arai60/Graph_BFS_DFS/word-ladder.md index f2b79c9..fb97d00 100644 --- a/arai60/Graph_BFS_DFS/word-ladder.md +++ b/arai60/Graph_BFS_DFS/word-ladder.md @@ -13,10 +13,10 @@ LeetCode URL: https://leetcode.com/problems/word-ladder/description/ /** * 時間計算量: O(n * m^2): * - O(n): wordList のクローン (n は wordList の要素数) - * - O(n * m^2): Map の生成 (n は wordListClone の要素数、 m は要素の文字数) + * - O(n * m^2): Map の生成 (n は wordList の要素数、 m は要素の文字数) * - O(n * m^2): wordQueue の要素の走査 (n は wordQueue の最大要素数、 m は要素の文字数) * 空間計算量: O(n * m) - * - O(n): wordListClone + * - O(n): wordList * - O(n * m): Map (n は Map の要素数、 m は value の要素数) * - O(n): wordQueue * - O(n): visitedWords @@ -175,10 +175,9 @@ class Solution { /** * 時間計算量: O(n * m^2): * - O(n): wordList のクローン (n は wordList の要素数) - * - O(n * m^2): Map の生成 (n は wordListClone の要素数、 m は要素の文字数) + * - O(n * m^2): Map の生成 (n は wordList の要素数、 m は要素の文字数) * - O(n * m^2): wordQueue の要素の走査 (n は wordQueue の最大要素数、 m は要素の文字数) * 空間計算量: O(n * m) - * - O(n): wordListClone * - O(n * m): Map (n は Map の要素数、 m は value の要素数) * - O(n): wordQueue * - O(n): visitedWords @@ -302,10 +301,9 @@ class Solution { * 解いた時間: 20分ぐらい * 時間計算量: O(n * m^2): * - O(n): wordList のクローン (n は wordList の要素数) - * - O(n * m^2): Map の生成 (n は wordListClone の要素数、 m は要素の文字数) + * - O(n * m^2): Map の生成 (n は wordList の要素数、 m は要素の文字数) * - O(n * m^2): wordQueue の要素の走査 (n は wordQueue の最大要素数、 m は要素の文字数) * 空間計算量: O(n * m) - * - O(n): wordListClone * - O(n * m): Map (n は Map の要素数、 m は value の要素数) * - O(n): wordQueue * - O(n): visitedWords @@ -377,7 +375,7 @@ class Solution { /** * 時間計算量: O(n * m^2): * - O(n): wordList のクローン (n は wordList の要素数) - * - O(n * m^2): Map の生成 (n は wordListClone の要素数、 m は要素の文字数) + * - O(n * m^2): Map の生成 (n は wordList の要素数、 m は要素の文字数) * - O(n * m^2): wordQueue の要素の走査 (n は wordQueue の最大要素数、 m は要素の文字数) * 空間計算量: O(n * m) * - O(n): wordListClone