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
25 changes: 25 additions & 0 deletions 392.IsSubsequence/divide.cpp
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&にはバインドできないので変数を用意する
Copy link

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 の値を利用しないため、参照渡しにする必然性がないように感じました。自分なら値渡しで書くと思います。

Copy link
Owner Author

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

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);
Copy link

Choose a reason for hiding this comment

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

この形の末尾再帰は容易にループに直せます。全体を while (1) {} でくくって、引数を代入に変えればいいのです。

}
};
25 changes: 25 additions & 0 deletions 392.IsSubsequence/divide_step2.cpp
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);
}
};
32 changes: 32 additions & 0 deletions 392.IsSubsequence/map.cpp
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;
Copy link

Choose a reason for hiding this comment

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

vector<int> letter_to_indices[128]; とすると、 map を省けるので、少し軽くなる可能性があります。

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) {
Copy link

Choose a reason for hiding this comment

The 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;
}
};
82 changes: 82 additions & 0 deletions 392.IsSubsequence/memo.md
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全体を探索することを避けることができる。
Copy link

Choose a reason for hiding this comment

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

s が 'a' 100 文字、 t が 'a' 10000 文字の場合、 letter_to_indices['a'] を毎回走査する必要があります。

Copy link
Owner Author

Choose a reason for hiding this comment

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

@nodchip
このケース見えておりませんでした🙇‍♂️ 他の箇所でレビュー頂いたbinary searchとvector letter_to_indices[128]を使う方式を追加しました。step4.cppです。

変更前
100 * 10000 = 10^5
変更後
100 * log_2(10000) = 100 * 13.28 = 1300

アクセスパターンがランダムアクセスになるため、データサイズによっては逆に遅くなるかもしれません。
この場合も認識しておきます🙇‍♂️


## ステップ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など

17 changes: 17 additions & 0 deletions 392.IsSubsequence/step1.cpp
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) {
Copy link

Choose a reason for hiding this comment

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

参照で受け取った場合、出力されるコードがアドレス経由で要素にアクセスするものになる可能性があり、処理が重くなる可能性があります。コンパイラーによる最適化が働けば問題ないのですが、期待しすぎるのもよくないと思います。 CPU のレジスターのビット幅に比べて小さい要素については、値で受け取ったほうがよいと思います。詳しくは『Effective C++ 第3版』をご覧ください。

Copy link
Owner Author

Choose a reason for hiding this comment

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

@nodchip
C++のコンパイラや実行環境によりサイズが大きくなることと組み込み型は普通値渡しと説明ございました🙇‍♂️
何度か読み返すようにします。

letters_to_subsequence.push(letter);
}

for (auto& letter : t) {
if (letter == letters_to_subsequence.front()) {
letters_to_subsequence.pop();
}
}

return letters_to_subsequence.empty();
}
};
14 changes: 14 additions & 0 deletions 392.IsSubsequence/step2.cpp
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]) {
Copy link

Choose a reason for hiding this comment

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

subsequence で配列外アクセスをしているように見えたのですが、規格上は問題ないのですね。

https://timsong-cpp.github.io/cppwp/n4950/string.access

Preconditions: pos <= size().
Returns: *(begin() + pos) if pos < size(). Otherwise, returns a reference to an object of type charT with value charT(), where modifying the object to any value other than charT() leads to undefined behavior.

読む人にとって紛らわしいため、自分なら subsequence_index == size() となるときにアクセスしないようなコードを書くと思います。

Copy link
Owner Author

Choose a reason for hiding this comment

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

ここも意識できておりませんでした。t側が大きい場合に、確かにエラ-が出そうですね。

subsequence_index++;
Copy link

Choose a reason for hiding this comment

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

subsequence_index == subsequence.size() の時に early return してもよいと思います。

}
}

return subsequence_index == subsequence.size();
}
};
13 changes: 13 additions & 0 deletions 392.IsSubsequence/step3.cpp
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();
}
};
31 changes: 31 additions & 0 deletions 392.IsSubsequence/step4.cpp
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;
}
};
16 changes: 16 additions & 0 deletions 392.IsSubsequence/step5.cpp
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();
}
};
15 changes: 15 additions & 0 deletions 392.IsSubsequence/two_pointers.cpp
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();
}
};