-
Notifications
You must be signed in to change notification settings - Fork 0
392. Is Subsequence #62
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| class Solution { | ||
| public: | ||
| bool isSubsequence(string s, string t) { | ||
| // 整数リテラルは非constなint&にはバインドできないので変数を用意する | ||
| int source_index = 0; | ||
| int target_index = 0; | ||
| return CheckIsSubsequence(source_index, s, target_index, t); | ||
| } | ||
|
|
||
| private: | ||
| bool CheckIsSubsequence(int& source_index, string& source, | ||
| int& target_index, string& target) { | ||
| if (source_index == source.size()) { | ||
| return true; | ||
| } | ||
| if (target_index == target.size()) { | ||
| return false; | ||
| } | ||
| if (source[source_index] == target[target_index]) { | ||
| source_index++; | ||
| } | ||
| target_index++; | ||
| return CheckIsSubsequence(source_index, source, target_index, target); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. この形の末尾再帰は容易にループに直せます。全体を while (1) {} でくくって、引数を代入に変えればいいのです。 |
||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| class Solution { | ||
| public: | ||
| bool isSubsequence(string s, string t) { | ||
| // 整数リテラルは非constなint&にはバインドできないので変数を用意する | ||
| int source_index = 0; | ||
| int target_index = 0; | ||
| return CheckIsSubsequence(source_index, s, target_index, t); | ||
| } | ||
|
|
||
| private: | ||
| bool CheckIsSubsequence(int source_index, string& source, | ||
| int target_index, string& target) { | ||
| if (source_index == source.size()) { | ||
| return true; | ||
| } | ||
| if (target_index == target.size()) { | ||
| return false; | ||
| } | ||
| if (source[source_index] == target[target_index]) { | ||
| source_index++; | ||
| } | ||
| target_index++; | ||
| return CheckIsSubsequence(source_index, source, target_index, target); | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| class Solution { | ||
| public: | ||
| bool isSubsequence(string s, string t) { | ||
| map<char, vector<int>> letter_to_indices; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| for (int i = 0; i < t.size(); i++) { | ||
| letter_to_indices[t[i]].push_back(i); | ||
| } | ||
|
|
||
| // 探索前なのでありえない値で初期化 | ||
| int current_index = -1; | ||
| for (char letter : s) { | ||
| if (!letter_to_indices.contains(letter)) { | ||
| return false; | ||
| } | ||
|
|
||
| bool is_matched = false; | ||
| for (int matched_index : letter_to_indices[letter]) { | ||
| if (current_index > matched_index) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. std::lower_bound() でインデックスを探すと、時間計算量的には速くなると思います。ただ、アクセスパターンがランダムアクセスになるため、データサイズによっては逆に遅くなるかもしれません。 |
||
| continue; | ||
| } | ||
| current_index = matched_index; | ||
| is_matched = true; | ||
| break; | ||
| } | ||
| // 順番を考慮してマッチしない場合 | ||
| if (!is_matched) { | ||
| return false; | ||
| } | ||
| } | ||
| return true; | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,82 @@ | ||
| ## ステップ1 | ||
| sが空の場合は、t内の文字を全て削除すれば作れるのでtrueですよね? | ||
|
|
||
| 思いついた方法は愚直にsをループで回し、tが含まれるのかチェック。 | ||
| tのインデックスを記憶しておき、sの次の文字の探索開始位置をそこにする。 | ||
| sの長さをm | ||
| tの長さをn | ||
| これだとO(m n) 10^4 * 100 なので10^6か | ||
|
|
||
| queueを使えば、二重ループにしなくても解けそう。 | ||
| >without disturbing the relative positions of the remaining characters. | ||
| 問題文に丁寧にこう書いてあるので、queueが向いてる。 | ||
| sの文字を順番に突っ込む。tを頭から探索して一致する文字があればqueueから取り出す。 | ||
| 最終的にqueueが空かどうかでとく。 | ||
| tの長さに釣られて | ||
| 時間計算量O(n) | ||
| 空間計算量O(n) | ||
|
|
||
| ## ステップ2 | ||
| step1に対して | ||
| queueに突っ込んだがforループで見ていく場合は前から見ていくので | ||
| queueにつっこむ意味はない | ||
| sの文字を見ていくのではなく、indexを見ていく | ||
| tを頭から見ていき、s[index]が現れた場合のみsのindexを更新する | ||
|
|
||
| leetcodeの解答より | ||
| バイナリーサーチ、グリーディー、ダイナミックプログラミングで解くことができる。 | ||
| =>どの回答もstep1の選択肢を考える際に思いつかなかった。 | ||
|
|
||
| ・divide.cppを実装 | ||
| 一文字ずつに分解して、sがtに含まれるのか再帰的に探索する | ||
| 文字をのまま渡すのではなく添字で管理する。 | ||
|
|
||
| sとtの先頭文字が一致すれば、sの添字を進める。そうでなければスキップする。 | ||
| 再帰呼び出しのたびにtの添字は進める。 | ||
| 時間はO(n)で、空間はO(n)で解くことができる。 | ||
|
|
||
| ・two_pointers.cpp | ||
| divide.cppだと再帰呼び出しのたびにスタックメモリを使用する(関数の戻り先か)ので再帰を使わずループに変更。 | ||
| =>この解法は初見で思いつきたかった。 | ||
| ループのたびにtを探索する添字はインクリメントする。 | ||
| sを探索する添字は探索中の文字が一致した場合のみとする。 | ||
| 最終的にsの添字がsの長さと一致していれば、Subsequenceを作ることができるということ。 | ||
| 時間はO(n)で、空間はO(1)で解くことができる。 | ||
|
|
||
| ・map.cpp | ||
| s側ではなくt側をmapに突っ込み文字に対する出現位置を全て入れる | ||
| sを頭から一文字ずつ確認する。 | ||
| 登場位置の制約を守りながら、全ての文字が登場するかどうかを確認することで解ける。 | ||
| この方法であれば毎回t全体を探索することを避けることができる。 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. s が 'a' 100 文字、 t が 'a' 10000 文字の場合、 letter_to_indices['a'] を毎回走査する必要があります。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @nodchip 変更前
|
||
|
|
||
| ## ステップ3 | ||
| **3回書き直しやりましょう、といっているのは、不自然なところや負荷の高いところは覚えられないからです。** | ||
|
|
||
| ## 他の方の解法 | ||
| >std::vector については何度か議論があったと思います。それらを意識したうえで使うのであれば大丈夫だと思います。過去のコメントを探してみることをお勧めいたします。 | ||
| 今回は使っていないが、vector<bool>の特殊化については認識しておく | ||
| https://cpprefjp.github.io/reference/vector/vector.html | ||
|
|
||
| >setを使う場合はcharacterPositionsの初期化がO(N)ではなくO(NlogN)になりそうかなと思ったのですが、 | ||
| insertする際の計算量見落としがちなので意識する。 | ||
| ・size_tを使うかintを使いかは文脈次第 | ||
| 過去の問題で受けたsize_tは, unsigned intであるということを意識して使う | ||
|
|
||
| >競合状態を避けるため、副作用のある書き方は現実的な範囲でできるだけ避けたほうがよいと思います。 | ||
| leetcodeだけの話だと見落としてしまいち | ||
| https://github.com/usatie/leetcode/pull/5/files/db0790afcbe0e57e141f025f902c1226890976b6#diff-96848deece2c6608d931aa2cb2aaf0ffd78c9fc4cd7337d89293dba090c204af | ||
|
|
||
|
|
||
| >s, t はどっちがどっちか分からないので避けたいですね。 | ||
| 書き換えられる範囲で意識しよう | ||
| https://github.com/philip82148/leetcode-arai60/pull/7 | ||
|
|
||
| 選択肢としてはhashmap、dp、two pointersか | ||
| https://github.com/Yoshiki-Iwasa/Arai60/pull/62/commits/09cc9ca0456c7df9c0f875c387207bc61727c787 | ||
|
|
||
| 辞書と二分探索を混ぜた解法 | ||
| 一見単純な問題でも選択肢たくさんあるんですね。 | ||
| https://github.com/fhiyo/leetcode/pull/55/commits/3bdbb38c39f00eba0213b5fb82d1cddd75e55537 | ||
|
|
||
| ## Discorなど | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| class Solution { | ||
| public: | ||
| bool isSubsequence(string s, string t) { | ||
| queue<char> letters_to_subsequence; | ||
| for (auto& letter : s) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 参照で受け取った場合、出力されるコードがアドレス経由で要素にアクセスするものになる可能性があり、処理が重くなる可能性があります。コンパイラーによる最適化が働けば問題ないのですが、期待しすぎるのもよくないと思います。 CPU のレジスターのビット幅に比べて小さい要素については、値で受け取ったほうがよいと思います。詳しくは『Effective C++ 第3版』をご覧ください。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @nodchip |
||
| letters_to_subsequence.push(letter); | ||
| } | ||
|
|
||
| for (auto& letter : t) { | ||
| if (letter == letters_to_subsequence.front()) { | ||
| letters_to_subsequence.pop(); | ||
| } | ||
| } | ||
|
|
||
| return letters_to_subsequence.empty(); | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| class Solution { | ||
| public: | ||
| bool isSubsequence(string subsequence, string text) { | ||
| int subsequence_index = 0; | ||
|
|
||
| for (int i = 0; i < text.size(); i++) { | ||
| if (subsequence[subsequence_index] == text[i]) { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. subsequence で配列外アクセスをしているように見えたのですが、規格上は問題ないのですね。 https://timsong-cpp.github.io/cppwp/n4950/string.access
読む人にとって紛らわしいため、自分なら subsequence_index == size() となるときにアクセスしないようなコードを書くと思います。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ここも意識できておりませんでした。t側が大きい場合に、確かにエラ-が出そうですね。 |
||
| subsequence_index++; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. subsequence_index == subsequence.size() の時に early return してもよいと思います。 |
||
| } | ||
| } | ||
|
|
||
| return subsequence_index == subsequence.size(); | ||
| } | ||
| }; | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| class Solution { | ||
| public: | ||
| bool isSubsequence(string subsequence, string text) { | ||
| int subsequence_index = 0; | ||
| for (int i = 0; i < text.size(); i++) { | ||
| if (subsequence[subsequence_index] == text[i]) { | ||
| subsequence_index++; | ||
| } | ||
| } | ||
|
|
||
| return subsequence_index == subsequence.size(); | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| // map.cppからレビューを反映したもの | ||
| class Solution { | ||
| public: | ||
| bool isSubsequence(string s, string t) { | ||
| // ASCII文字列として格納する | ||
| vector<int>letters_to_indices[128]; | ||
| for (int i = 0; i < t.size(); i++) { | ||
| letters_to_indices[static_cast<int>(t[i])].push_back(i); | ||
| } | ||
|
|
||
| // 探索前なのでありえない値で初期化 | ||
| int current_index = -1; | ||
| for (char letter : s) { | ||
| int letter_index = static_cast<int>(letter); | ||
| if (letters_to_indices[letter_index].empty()) { | ||
| return false; | ||
| } | ||
|
|
||
| auto it = lower_bound(letters_to_indices[letter_index].begin(), | ||
| letters_to_indices[letter_index].end(), | ||
| current_index + 1); | ||
| if (it == letters_to_indices[letter_index].end()) { | ||
| return false; | ||
| } | ||
|
|
||
| // 順番を考慮してマッチしない場合 | ||
| current_index = *it; | ||
| } | ||
| return true; | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| class Solution { | ||
| public: | ||
| bool isSubsequence(string subsequence, string text) { | ||
| int subsequence_index = 0; | ||
| for (int i = 0; i < text.size(); i++) { | ||
| if (subsequence_index == subsequence.size()) { | ||
| break; | ||
| } | ||
| if (subsequence[subsequence_index] == text[i]) { | ||
| subsequence_index++; | ||
| } | ||
| } | ||
|
|
||
| return subsequence_index == subsequence.size(); | ||
| } | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| class Solution { | ||
| public: | ||
| bool isSubsequence(string s, string t) { | ||
| int source_index = 0; | ||
| int target_index = 0; | ||
| while (source_index < s.size() && target_index < t.size()) { | ||
| if (s[source_index] == t[target_index]) { | ||
| source_index++; | ||
| } | ||
| target_index++; | ||
| } | ||
|
|
||
| return source_index == s.size(); | ||
| } | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CheckIsSubsequence() を呼び出したあとに source_index、 target_index の値を利用しないため、参照渡しにする必然性がないように感じました。自分なら値渡しで書くと思います。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nodchip レビューありがとうございます。確かに不要でした。
『Effective C++ 第3版』の21項に書かれていた通り根絶やしにしようとしておりました。
『Effective C++ 第3版』の20項に組み込み型に関しては普通値渡しと説明ございました。
55000b0