From 0453bcaa1895add00552512117c94b00c062a399 Mon Sep 17 00:00:00 2001 From: Ryotaro Kurita Date: Thu, 28 Nov 2024 23:46:29 +0900 Subject: [PATCH 1/2] finish --- 139.WordBreak/bfs.cpp | 31 ++++++++++++++++++++++++ 139.WordBreak/dfs.cpp | 33 +++++++++++++++++++++++++ 139.WordBreak/memo.md | 53 +++++++++++++++++++++++++++++++++++++++++ 139.WordBreak/step1.cpp | 36 ++++++++++++++++++++++++++++ 139.WordBreak/step2.cpp | 36 ++++++++++++++++++++++++++++ 139.WordBreak/step3.cpp | 36 ++++++++++++++++++++++++++++ 139.WordBreak/trie.cpp | 42 ++++++++++++++++++++++++++++++++ 7 files changed, 267 insertions(+) create mode 100644 139.WordBreak/bfs.cpp create mode 100644 139.WordBreak/dfs.cpp create mode 100644 139.WordBreak/memo.md create mode 100644 139.WordBreak/step1.cpp create mode 100644 139.WordBreak/step2.cpp create mode 100644 139.WordBreak/step3.cpp create mode 100644 139.WordBreak/trie.cpp diff --git a/139.WordBreak/bfs.cpp b/139.WordBreak/bfs.cpp new file mode 100644 index 0000000..987cc71 --- /dev/null +++ b/139.WordBreak/bfs.cpp @@ -0,0 +1,31 @@ +class Solution { +public: + bool wordBreak(string s, vector& wordDict) { + string_view word = s; + set word_dict(wordDict.begin(), wordDict.end()); + queue dividing_positions; + dividing_positions.push(0); + vector visited(s.size(), false); + + while (!dividing_positions.empty()) { + int start_position = dividing_positions.front(); + dividing_positions.pop(); + + if (start_position == word.size()) { + return true; + } + + for (int end_position = start_position + 1; end_position <= word.size(); end_position++) { + if (visited[end_position]) { + continue; + } + if (word_dict.contains(word.substr(start_position, end_position - start_position))) { + dividing_positions.push(end_position); + visited[end_position] = true; + } + } + } + + return false; + } +}; diff --git a/139.WordBreak/dfs.cpp b/139.WordBreak/dfs.cpp new file mode 100644 index 0000000..764bacf --- /dev/null +++ b/139.WordBreak/dfs.cpp @@ -0,0 +1,33 @@ +class Solution { +public: + bool wordBreak(string s, vector& wordDict) { + map word_to_validity; + return CheckWordBreak(s, wordDict, word_to_validity); + } + +private: + bool CheckWordBreak(const string_view& target, const vector& word_dict, + map& word_to_validity) { + if (word_to_validity.contains(target)) { + return word_to_validity[target]; + } + + if (target.empty()) { + return true; + } + + for (const auto word : word_dict) { + // targetがwordから始まっているかどうかをチェック + if (target.starts_with(word)) { + string_view remaining = target.substr(word.size()); + if (CheckWordBreak(remaining, word_dict, word_to_validity)) { + word_to_validity[target] = true; + return true; + } + } + } + + word_to_validity[target] = false; + return false; + } +}; diff --git a/139.WordBreak/memo.md b/139.WordBreak/memo.md new file mode 100644 index 0000000..e9d7b60 --- /dev/null +++ b/139.WordBreak/memo.md @@ -0,0 +1,53 @@ +## ステップ1 +与えられた文字列を分割しながら、wordDict内に存在するのかチェックする +wordDictはvectorからsetに入れ直してcontainsを使いたい + +ある地点で分割して、その地点から左側と右側に分ける +左側が辞書内に存在していれば右側をチェック。 +右側に対して、ある地点で分割をして左側と右側に分け上記の作業をくりかえる + +時間計算量O(n^2) <-自信ないので間違っていたら指摘お願いいたします +contains関数の計算量はlog n +再帰とforループ部分はn(n+1) / 2で省略するとn^2 + +空間計算量 +O(n) + +## ステップ2 +・substr() によって string のコピーが作られるのを避ける +string_viewが使えそう。string_viewのsubstrの計算量はO(1) +https://www.modernescpp.com/index.php/c-17-avoid-copying-with-std-string-view/ +https://en.cppreference.com/w/cpp/string/basic_string_view + +早いからといって毎回string_viewを使うのではなく破壊的(恒久的)な変更を行いたい場合はstringを使う +今回は単にwordDict内に存在するかチェックしたいだけなのでコピーの発生しないstring_viewを使う +https://medium.com/@chittaranjansethi/exploring-the-differences-std-string-vs-std-string-view-d1c7015162f0 + +## ステップ3 +**3回書き直しやりましょう、といっているのは、不自然なところや負荷の高いところは覚えられないからです。** + +## 他の方の解法 +Auxって言葉知らなかった Auxiliary(補助) +https://github.com/philip82148/leetcode-arai60/pull/8 +こちらの方は深さ優先探索を用いている。 +幅優先探索で解くことができる。またTrieというものがある +https://github.com/Yoshiki-Iwasa/Arai60/pull/67/commits/3bd8c415249792da181d1351a108f3208c62ffce +https://github.com/fhiyo/leetcode/pull/40 +https://github.com/sakupan102/arai60-practice/pull/40 +https://github.com/SuperHotDogCat/coding-interview/pull/23 + +## 他の解法など +幅優先探索をbfs.cppに実装 +startとendの文字を分割する情報を持っておく。 +開始位置を0から初めて終了地点を動かしながら探索を行う。分割できる点があれば、それを次の開始位置として同様に探索する + +深さ優先探索をdfs.cppに実装 +starts_withがc++のstring_viewにも存在していた + +Trieはtrie.cppに実装(写経) +Trie +https://en.wikipedia.org/wiki/Trie + +Trieの練習問題は下記(どこかでやってみる) +https://leetcode.com/problems/implement-trie-prefix-tree/editorial/ + diff --git a/139.WordBreak/step1.cpp b/139.WordBreak/step1.cpp new file mode 100644 index 0000000..cf725f7 --- /dev/null +++ b/139.WordBreak/step1.cpp @@ -0,0 +1,36 @@ +class Solution { +public: + bool wordBreak(string s, vector& wordDict) { + set word_dict(wordDict.begin(), wordDict.end()); + map word_to_validity; + + return CheckWordBreak(s, word_dict, word_to_validity); + } + +private: + bool CheckWordBreak(string word, const set& word_dict, map& word_to_validity) { + if (word_dict.contains(word)) { + return true; + } + + if (word_to_validity.contains(word)) { + return word_to_validity[word]; + } + + for (int i = 0; i < word.size(); i++) { + string left_side_word = word.substr(0, i + 1); + + if (word_dict.contains(left_side_word)) { + string right_side_word = word.substr(i + 1); + bool rest_word_validity = CheckWordBreak(right_side_word, word_dict, word_to_validity); + if (rest_word_validity) { + word_to_validity[word] = true; + return true; + } + } + } + + word_to_validity[word] = false; + return false; + } +}; diff --git a/139.WordBreak/step2.cpp b/139.WordBreak/step2.cpp new file mode 100644 index 0000000..ffb0611 --- /dev/null +++ b/139.WordBreak/step2.cpp @@ -0,0 +1,36 @@ +class Solution { +public: + bool wordBreak(string s, vector& wordDict) { + set word_dict(wordDict.begin(), wordDict.end()); + map word_to_validity; + + return CheckWordBreakable(s, word_dict, word_to_validity); + } + +private: + bool CheckWordBreakable(string_view word, const set& word_dict, + map& word_to_validity) { + if (word_dict.contains(word)) { + return true; + } + + if (word_to_validity.contains(word)) { + return word_to_validity[word]; + } + + for (int i = 0; i < word.size(); i++) { + string_view left_side_word = word.substr(0, i + 1); + + if (word_dict.contains(left_side_word)) { + string_view right_side_word = word.substr(i + 1); + if (CheckWordBreakable(right_side_word, word_dict, word_to_validity)) { + word_to_validity[word] = true; + return true; + } + } + } + + word_to_validity[word] = false; + return false; + } +}; diff --git a/139.WordBreak/step3.cpp b/139.WordBreak/step3.cpp new file mode 100644 index 0000000..479b34b --- /dev/null +++ b/139.WordBreak/step3.cpp @@ -0,0 +1,36 @@ +class Solution { +public: + bool wordBreak(string s, vector& wordDict) { + set word_dict(wordDict.begin(), wordDict.end()); + map word_to_validity; + + return CheckWordBreakable(s, word_dict, word_to_validity); + } + +private: + bool CheckWordBreakable(string_view word, const set& word_dict, + map& word_to_validity) { + if (word_dict.contains(word)) { + return true; + } + + if (word_to_validity.contains(word)) { + return word_to_validity[word]; + } + + for (int i = 0; i < word.size(); i++) { + auto left_word = word.substr(0, i); + + if (word_dict.contains(left_word)) { + auto right_word = word.substr(i + 1); + if (CheckWordBreakable(right_word, word_dict, word_to_validity)) { + word_to_validity[word] = true; + return true; + } + } + } + + word_to_validity[word] = false; + return false; + } +}; diff --git a/139.WordBreak/trie.cpp b/139.WordBreak/trie.cpp new file mode 100644 index 0000000..d85270a --- /dev/null +++ b/139.WordBreak/trie.cpp @@ -0,0 +1,42 @@ +class Solution { +public: + bool wordBreak(string s, vector& wordDict) { + TrieNode* root = new TrieNode(); + for (auto word : wordDict) { + TrieNode* node = root; + for (char letter : word) { + if (!node->children.contains(letter)) { + node->children[letter] = new TrieNode(); + } + node = node->children[letter]; + } + node->is_word = true; + } + + vector is_segmented(s.size()); + for (int start = 0; start < s.size(); start++) { + if (start == 0 || is_segmented[start - 1]) { + TrieNode* node = root; + for (int end = start; end < s.size(); end++) { + char letter = s[end]; + if (!node->children.contains(letter)) { + break; + } + + node = node->children[letter]; + if (node->is_word) { + is_segmented[end] = true; + } + } + } + } + return is_segmented.back(); + } + +private: + struct TrieNode { + bool is_word; + map children; + TrieNode() : is_word(false), children(map()){} + }; +}; From 6ba5f489083be669f5e0002c55620e588843bcfe Mon Sep 17 00:00:00 2001 From: Ryotaro Kurita Date: Thu, 5 Dec 2024 01:27:22 +0900 Subject: [PATCH 2/2] mod reflecting review --- 139.WordBreak/bfs_step2.cpp | 36 ++++++++++++++++++++++ 139.WordBreak/dfs.cpp | 2 +- 139.WordBreak/for.cpp | 31 +++++++++++++++++++ 139.WordBreak/memo.md | 6 ++++ 139.WordBreak/step1.cpp | 2 +- 139.WordBreak/step4.cpp | 38 +++++++++++++++++++++++ 139.WordBreak/trie_step2.cpp | 59 ++++++++++++++++++++++++++++++++++++ 7 files changed, 172 insertions(+), 2 deletions(-) create mode 100644 139.WordBreak/bfs_step2.cpp create mode 100644 139.WordBreak/for.cpp create mode 100644 139.WordBreak/step4.cpp create mode 100644 139.WordBreak/trie_step2.cpp diff --git a/139.WordBreak/bfs_step2.cpp b/139.WordBreak/bfs_step2.cpp new file mode 100644 index 0000000..29be72e --- /dev/null +++ b/139.WordBreak/bfs_step2.cpp @@ -0,0 +1,36 @@ +class Solution { +public: + bool wordBreak(string str, vector& wordDict) { + set word_dict(wordDict.begin(), wordDict.end()); + queue dividing_positions; + dividing_positions.push(0); + // 分割地点として処理済みか + vector proceeded(str.size() + 1, FALSE); + + while (!dividing_positions.empty()) { + int start_position = dividing_positions.front(); + dividing_positions.pop(); + + if (start_position == str.size()) { + return true; + } + // 文字列の長さの最大値で打ち切ったほうが、計算量が落ち、速くなると思います。 + for (int end_position = start_position + 1; end_position <= str.size(); end_position++) { + if (proceeded[end_position]) { + continue; + } + // begin() end()はそれぞれイテレーター + if (word_dict.contains(string_view(str.begin() + start_position, str.begin() + end_position))) { + dividing_positions.push(end_position); + proceeded[end_position] = TRUE; + } + } + } + + return false; + } + +private: + static constexpr char TRUE = 1; + static constexpr char FALSE = 0; +}; diff --git a/139.WordBreak/dfs.cpp b/139.WordBreak/dfs.cpp index 764bacf..3b1a44c 100644 --- a/139.WordBreak/dfs.cpp +++ b/139.WordBreak/dfs.cpp @@ -16,7 +16,7 @@ class Solution { return true; } - for (const auto word : word_dict) { + for (const auto& word : word_dict) { // targetがwordから始まっているかどうかをチェック if (target.starts_with(word)) { string_view remaining = target.substr(word.size()); diff --git a/139.WordBreak/for.cpp b/139.WordBreak/for.cpp new file mode 100644 index 0000000..cafdfdd --- /dev/null +++ b/139.WordBreak/for.cpp @@ -0,0 +1,31 @@ +class Solution { +public: + bool wordBreak(string str, vector& wordDict) { + set word_dict(wordDict.begin(), wordDict.end()); + // 分割地点として処理済みか + vector proceeded(str.size() + 1, FALSE); + proceeded[0] = TRUE; + + for (int start_position = 0; start_position < str.size(); start_position++) { + if (!proceeded[start_position]) { + continue; + } + + for (int end_position = start_position + 1; end_position <= str.size(); end_position++) { + if (proceeded[end_position]) { + continue; + } + + if (word_dict.contains(string_view(str.begin() + start_position, str.begin() + end_position))) { + proceeded[end_position] = TRUE; + } + } + } + + return proceeded[str.size()] == TRUE; + } + +private: + static constexpr char TRUE = 1; + static constexpr char FALSE = 0; +}; diff --git a/139.WordBreak/memo.md b/139.WordBreak/memo.md index e9d7b60..12c2a2d 100644 --- a/139.WordBreak/memo.md +++ b/139.WordBreak/memo.md @@ -51,3 +51,9 @@ https://en.wikipedia.org/wiki/Trie Trieの練習問題は下記(どこかでやってみる) https://leetcode.com/problems/implement-trie-prefix-tree/editorial/ +>vector は bool 型に対して特殊化されている。 +https://cpprefjp.github.io/reference/vector/vector.html +>配列のように連続して格納されているとは限らない + +これが遅くなる原因か +https://zenn.dev/stmo/articles/d622a40b31fbee diff --git a/139.WordBreak/step1.cpp b/139.WordBreak/step1.cpp index cf725f7..5e77e95 100644 --- a/139.WordBreak/step1.cpp +++ b/139.WordBreak/step1.cpp @@ -8,7 +8,7 @@ class Solution { } private: - bool CheckWordBreak(string word, const set& word_dict, map& word_to_validity) { + bool CheckWordBreak(const string& word, const set& word_dict, map& word_to_validity) { if (word_dict.contains(word)) { return true; } diff --git a/139.WordBreak/step4.cpp b/139.WordBreak/step4.cpp new file mode 100644 index 0000000..7c92829 --- /dev/null +++ b/139.WordBreak/step4.cpp @@ -0,0 +1,38 @@ +class Solution { +public: + bool wordBreak(string str, vector& wordDict) { + set word_dict(wordDict.begin(), wordDict.end()); + // 切り出されたstringが有効なのか関数の中で記録する + map substring_to_breakable; + return CheckWordBreakable(str, word_dict, substring_to_breakable); + } + +private: + // string_viewのstrはデータを持っていない + // 元の文字列データ(ここではwordBreak関数のstring str)へのポインターを管理している + // コピーを発生させないため + bool CheckWordBreakable(string_view str, const set& word_dict, + map& substring_to_breakable) { + if (word_dict.contains(str)) { + return true; + } + + if (substring_to_breakable.contains(str)) { + return substring_to_breakable[str]; + } + + for (const auto& word : word_dict) { + if (!str.starts_with(word)) { + continue; + } + string_view remain = str.substr(word.size()); + if (CheckWordBreakable(remain, word_dict, substring_to_breakable)) { + substring_to_breakable[str] = true; + return true; + } + } + + substring_to_breakable[str] = false; + return false; + } +}; diff --git a/139.WordBreak/trie_step2.cpp b/139.WordBreak/trie_step2.cpp new file mode 100644 index 0000000..c4c54cf --- /dev/null +++ b/139.WordBreak/trie_step2.cpp @@ -0,0 +1,59 @@ +class Solution { +public: + bool wordBreak(string s, vector& wordDict) { + auto root = BuildTrieNode(wordDict); + + vector is_segmented(s.size()); + for (int start = 0; start < s.size(); start++) { + if (start == 0 || is_segmented[start - 1]) { + if (SearchString(root, s, start, is_segmented)) { + is_segmented[start] = TRUE; + } + } + } + return is_segmented.back(); + } + +private: + static constexpr char TRUE = 1; + static constexpr char FALSE = 0; + + struct TrieNode { + bool is_word; + map children; + TrieNode() : is_word(false), children(map()){} + }; + + unique_ptr BuildTrieNode(const vector word_dict) { + // スマートポインタで管理 + unique_ptr root = make_unique(); + for (auto word : word_dict) { + TrieNode* node = root.get(); + for (char letter : word) { + if (!node->children.contains(letter)) { + node->children[letter] = new TrieNode(); + } + node = node->children[letter]; + } + node->is_word = TRUE; + } + return root; + } + + bool SearchString(const unique_ptr& root, const string& str, + int start, vector& is_segmented) { + auto node = root.get(); + for (int end = start; end < str.size(); end++) { + char letter = str[end]; + if (!node->children.contains(letter)) { + break; + } + + node = node->children[letter]; + if (node->is_word) { + is_segmented[end] = TRUE; + } + } + return is_segmented[str.size() - 1]; + } +};