Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions 139.WordBreak/bfs.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
string_view word = s;
set<string_view> word_dict(wordDict.begin(), wordDict.end());
queue<int> dividing_positions;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

queue<int> を使って、分割地点の候補を管理するより、 start_position を 0 から word.size() - 1 まで回したほうがシンプルだと思いました。ただし、 start_position が分割の開始地点の候補でないことを確認するため、

if (!visited[start_position]) {
    continue;
}

をどこかに入れることになると思います。

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.

@nodchip
for.cppとして新たにファイルを追加しました。

dividing_positions.push(0);
vector<bool> visited(s.size(), false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

vector は特殊化されており、速度がやや遅くなる可能性があります。
https://cpprefjp.github.io/reference/vector/vector.html
vector<char> visited(s.size(), false); を使ったほうが速くなるかもしれません。

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.

@nodchip

メモリの効率化を行っているから[]の場合、インデックスでアクセスする場合に遅い場合があるのですね。。。
この辺り読んでみました。
https://qiita.com/voidhoge/items/383244bad2d728a18dbe
https://learn.microsoft.com/ja-jp/cpp/standard-library/vector-bool-class?view=msvc-170
https://kmyk.github.io/blog/blog/2017/06/07/bitset-vector-bool/

vectorに置き換えてみました。
6ba5f48

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

visited という名前は、グラフ等で探索済みのノードを表すのであれば自然だと思うのですが、今回のように、すでに分割地点として調べたかどうかを表すのには不適切だと感じました。あまりいい変数名が思いつかないのですが、分割済みである divided や、処理済みである proceeded あたりはいかがでしょうか?


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++) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

文字列の長さの最大値で打ち切ったほうが、計算量が落ち、速くなると思います。

if (visited[end_position]) {
continue;
}
if (word_dict.contains(word.substr(start_position, end_position - start_position))) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

文字列のコピーを避けるため、
string_view(word.begin() + start_position, word.begin() + end_position)
とできるでしょうか。

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.

@nodchip
上の方でstring_view word = s;としていたのですが微妙でしたね。。。

string_view(word.begin() + start_position, word.begin() + end_position)
この書き方知りませんでした🙇‍♂️ありがとうございます。

bfs_step2.cppに追加しました。
6ba5f48

dividing_positions.push(end_position);
visited[end_position] = true;
}
}
}

return false;
}
};
36 changes: 36 additions & 0 deletions 139.WordBreak/bfs_step2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
class Solution {
public:
bool wordBreak(string str, vector<string>& wordDict) {
set<string_view> word_dict(wordDict.begin(), wordDict.end());
queue<int> dividing_positions;
dividing_positions.push(0);
// 分割地点として処理済みか
vector<char> 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;
};
33 changes: 33 additions & 0 deletions 139.WordBreak/dfs.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
map<string_view, bool> word_to_validity;
return CheckWordBreak(s, wordDict, word_to_validity);
}

private:
bool CheckWordBreak(const string_view& target, const vector<string>& word_dict,
map<string_view, bool>& 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;
}
};
31 changes: 31 additions & 0 deletions 139.WordBreak/for.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
class Solution {
public:
bool wordBreak(string str, vector<string>& wordDict) {
set<string_view> word_dict(wordDict.begin(), wordDict.end());
// 分割地点として処理済みか
vector<char> 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;
};
59 changes: 59 additions & 0 deletions 139.WordBreak/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
## ステップ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/

>vector は bool 型に対して特殊化されている。
https://cpprefjp.github.io/reference/vector/vector.html
>配列のように連続して格納されているとは限らない

これが遅くなる原因か
https://zenn.dev/stmo/articles/d622a40b31fbee
36 changes: 36 additions & 0 deletions 139.WordBreak/step1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
set<string> word_dict(wordDict.begin(), wordDict.end());
map<string, bool> word_to_validity;

@frinfo702 frinfo702 Nov 30, 2024

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.

@frinfo702
レビューありがとうございます。
確かに不親切ですね。。読み手のことを考えコメントも積極的に使おうと思います。
別レビュー事項を反映し変数名も変わっておりますがコメントを追記しました。

step4.cpp
6ba5f48


return CheckWordBreak(s, word_dict, word_to_validity);
}

private:
bool CheckWordBreak(const string& word, const set<string>& word_dict, map<string, bool>& 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)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ここ、

            if (!word_dict.contains(left_side_word)) {
                continue;
            }

と書くと深いネストを一段改善できると思います

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;
}
};
36 changes: 36 additions & 0 deletions 139.WordBreak/step2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
set<string_view> word_dict(wordDict.begin(), wordDict.end());
map<string_view, bool> word_to_validity;

return CheckWordBreakable(s, word_dict, word_to_validity);
}

private:
bool CheckWordBreakable(string_view word, const set<string_view>& word_dict,
map<string_view, bool>& word_to_validity) {
if (word_dict.contains(word)) {
return true;
}

if (word_to_validity.contains(word)) {

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

Choose a reason for hiding this comment

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

あ、誤読ですね。これ false の場合使われていますね。

return word_to_validity[word];
}

for (int i = 0; i < word.size(); 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.

ここ、word_dict 側で調べるのも手で、
https://en.cppreference.com/w/cpp/string/basic_string_view/starts_with
starts_with が C++20 からあります。

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.

word_dict側で調べる方式でstep4に追加しました。
6ba5f48

string_view left_side_word = word.substr(0, i + 1);

@philip82148 philip82148 Nov 29, 2024

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がstring_viewなので動いていますが、wordがstringだった場合動かなくなります。
でも、wordがstringでも、以下のコードなら動きます。

auto left_side_word_str = word.substr(0, i + 1);
string_view left_side_word = left_side_word_str;

どうしてか説明できますでしょうか…?(C++って難しいですね)
(理解して使われているかが気になりました)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@philip82148 さん

どうしてか説明できますでしょうか…?

私も守りきれているわけではないですが、レビューで疑問文を使うパターンはあまり多くなく、これはそのパターンから外れているように思います。通常は、提案と意図の確認ですね。

  • 提案「こちらの方がいいのではないでしょうか?」
  • 意図の確認「A, B, C が選択としてあると思いますが、A を選んだ意図はなんでしょうか?」

この2つは自分が知りようがないので聞いています。自分が知っているならば、書いてしまったほうがコミュニケーションコストが低いですからね。
コードレビューの目的は、よりよいコードにすることなので、この2つはそれに向いています。

ここの場は、「ソフトウェアエンジニアの入口レベルに到達するための訓練」という側面(もう一つの目的)もあるので、面接の情報提供、言葉使いや理解の修正が行われていることもあります。

ただ、この疑問文は「私の知っている正解を述べられるか」で疑問文を使う場面ではありません。

そういうわけで、この場合は「このように書いた場合はこの理由で動きません。(あれば)信頼できるソースへのポインターはこれです。」とまで書いてください。

@philip82148 philip82148 Nov 30, 2024

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@oda 承知しました!
すみません、では修正してもう一度コメントさせていただきますmm

元のコードが悪い訳ではありませんが、string_viewを使うときは、文字列データ本体を持っているかを意識する必要があるということが元々言いたかったです。
それをwordstringだと動かないという話で説明したいと思います。

wordstringの場合、word.substr()は新しいstringを生成します。これは元のwordをコピーしたもので、文字列データ本体(メモリ)が別に用意されます。
string_viewは、文字列データ本体誰かに持ってもらって、それを覗き見るデータ構造になっています。
(先頭ポインタと文字数だけ保存していて、文字列データ本体は誰か別の人が持っている状態。)
なので、

string_view left_side_word = word_str.substr(0, i + 1);

だと、word_str.substr()で一時的にstringが生成され(これを一時オブジェクトAとします)、その先頭ポインタとサイズがstring_viewに保存されますが、この式全体が実行された直後に一時オブジェクトAはdestructされ、文字列データ本体(メモリ)が解放されてしまいます。
しかし、以下のようにしていれば、一時オブジェクトAはleft_side_word_strというローカル変数になり、(同じブロックスコープ内では)destructされなくなります。なので動きます。

auto left_side_word_str = word.substr(0, i + 1);
string_view left_side_word = left_side_word_str;

なので、string_viewを使うときは、文字列データ本体を持っているかを意識する必要があります。
なお例えば、word文字列データ本体を持っているのはwordBreak()string型の引数sです。

文字列データ本体=charの配列

※ちなみに僕は一瞬元のコードを見てびっくりしてしまった(なんでこれ動いてるんだ?→ああwordってstring_viewなのか)のでこの話を出したのですが、よくよく考えてみるとそんなに変なコードでもないのかなと思い。
ただ、個人的な好みとしてはauto left_side_word = ...のようにしておくとこういう違和感(+途中でやっぱstringの方がいいやとなった時に変更箇所)を生まなくていいと思っています(右辺がstringでもstring_viewでも問題ない)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

後ついでに left_side_wordって長いのでleftprefixはどうでしょうか?
(一応どなたか講師役の方リアクションしていただけると)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

https://abseil.io/tips/1

Because the string_view does not own its data, any strings pointed to by the string_view (just like a const char*) must have a known lifespan, and must outlast the string_view itself.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@philip82148 良くなったと思います。ありがとうございます。

そうですね。簡単にまとめます。
string_view は典型的にはポインターと長さの組で、文字列のオーナーシップを持っていないので、指しているデータが破壊されると、string_view 自体が invalidate になりますね。

string_view left_side_word = word.substr(0, i + 1);

の行を見たときに、word が string だとすると、これは一時オブジェクトを返し、left_side_word は生存期間が終了している string を指していることになりますね。

こういうとき、たとえば、「substr が string を返すかと思い生存期間の問題があるように勘違いした。一回 auto で受けたら勘違いしないのでは。」というのは、よりよいコードの提案(コードレビューの類型)として成立していますね。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

string_view は C++17 からなのであまり馴染がない人は多いでしょう。(私は昔、StringPiece という似たような非標準ライブラリーを使っていましたので馴染みがないです。)

提案自体へのコメントとしては、

  • 元のコードの設計思想として、wordBreak がオーナーシップを持って、CheckWordBreakable は string_view だけで処理するということかと思うので、それが通じれば自然かと思います。
  • auto で受けた後に string_view に代入しているが、それだったら、単に auto で受けるのも一つです。ただ、これは勘違いさせたままで続きを読ませることになります。
  • するとコメントを一言書くくらいがバランスとしていいかなあ。たとえば、// Note: This substr is of type string_view, not string. Be mindful of ownership and lifetime issues.

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.

@philip82148
レビューとまた詳細な質問ありがとうございます。
string_viewを用いたのは以下のコメントを読んで調べたのがスタートです。
philip82148/leetcode-swejp#8 (comment)

なので、string_viewを使うときは、誰が文字列データ本体を持っているかを意識する必要があります。なお例えば、wordの文字列データ本体を持っているのはwordBreak()のstring型の引数sです。

この理解でした。今回はコピーを作って、コピー文字列に対して何かしたい、返却したいといった問題ではなかったのでstring_viewを使うことにしました。

個人的な好みとしてはauto left_side_word = ...のようにしておくとこういう違和感(+途中でやっぱstringの方がいいやとなった時に変更箇所)を生まなくていいと思っています

これは確かにその通りでautoだとstringなのかstring_viewなのかとなりますね。誤解のないようなコードの書き方を心がけたいと思います🙇‍♂️

@Ryotaro25 Ryotaro25 Dec 4, 2024

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.

@oda レビューありがとうございます。

string_view は C++17 からなのであまり馴染がない人は多いでしょう。(私は昔、StringPiece という似たような非標準ライブラリーを使っていましたので馴染みがないです。)

自分もこの問題を通してstring_viewを知りました。string_viewがどういったものなのかコード内にコメントで書いておくべきでしょうか?

まだGitに上げていないのですが下記のようなコメントを追記する予定です。
// string_viewのstrはデータを持っていない
// 元の文字列データ(ここではwordBreak関数のstring str)へのポインターを管理している
// コピーを発生させないため

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

いや、それは不要でしょう。それをするとほとんどすべての関数呼び出しにその粒度でコメントをつけることになりますね。

読む人が、「適切なドキュメントに当たることができる人物である」ことは仮定して書いていいです。これは知識を仮定しているというよりは「調べる能力」を仮定しています。
https://docs.google.com/presentation/d/1Ny4kmHE2FZMI0AuPxImokweGoAE73RAGivjDJg0kG80/edit#slide=id.g2194639112d_0_294


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;
}
};
36 changes: 36 additions & 0 deletions 139.WordBreak/step3.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
set<string_view> word_dict(wordDict.begin(), wordDict.end());
map<string_view, bool> word_to_validity;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

validity という単語が何を表そうとしているのか、直感的ではないように感じました。また、 word とありますが、実際には複数の word が結合されている場合もあると思いました。 substring_to_breakable はいかがでしょうか?

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.

@nodchip
もう少し意味のある変数名を練らないとですね。
substring_to_breakableを使わせていただきました🙇


return CheckWordBreakable(s, word_dict, word_to_validity);
}

private:
bool CheckWordBreakable(string_view word, const set<string_view>& word_dict,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

string_view word の word が単語一つではなくて結合しているかを知りたいものなのが結構意外感があります。

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.

@oda いい単語が出てこなかったので単純にstrとしました。

map<string_view, bool>& 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;
}
};
38 changes: 38 additions & 0 deletions 139.WordBreak/step4.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
class Solution {
public:
bool wordBreak(string str, vector<string>& wordDict) {
set<string_view> word_dict(wordDict.begin(), wordDict.end());
// 切り出されたstringが有効なのか関数の中で記録する
map<string_view, bool> 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<string_view>& word_dict,
map<string_view, bool>& 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;
}
};
42 changes: 42 additions & 0 deletions 139.WordBreak/trie.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
class Solution {
public:
bool wordBreak(string s, vector<string>& wordDict) {
TrieNode* root = new TrieNode();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

これ、new したのを delete していないのでメモリーリークしています。

また、できれば Trie の構築はメンバ関数にするときれいでしょう。

@Ryotaro25 Ryotaro25 Dec 4, 2024

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.

@oda
メモリへの意識がまだまだ低いです。deleteを書くのではなく、smart pointerを使う方式に変更しました。
また、Trieの構築と探索をメンバ関数に変更。
vectorをvectorへ変更しました。

trie_step2.cpp
6ba5f48

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

あ、単に TrieNode root; でもいいでしょう。あとで、&root とする必要がありますが。

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<bool> 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;
}
}
Comment on lines +19 to +30

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ここもメンバ関数にしてしまってもいいかもしれません。

}
}
return is_segmented.back();
}

private:
struct TrieNode {
bool is_word;
map<char, TrieNode*> children;
TrieNode() : is_word(false), children(map<char, TrieNode*>()){}
};
};
Loading