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
94 changes: 94 additions & 0 deletions 242_valid_anagram/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# 242. Valid Anagram

https://leetcode.com/problems/valid-anagram/

## Comments

### step1

* `map` と `unordered_map`
* https://cpprefjp.github.io/reference/map/map.html
* https://cpprefjp.github.io/reference/unordered_map/unordered_map.html
* `map` は binary tree、`unordered_map` は hash で実装されているらしい。
* ああだから `map` の計算量は平均・最悪とも O(logN) で
* `unordered_map` は償却 O(1)、ただし最悪 O(N) なのか (合ってる?)
* hash strategy がイケてなくて全部衝突すると O(N)
* 全く同じことが `set` vs. `unordered_set` でも言える
* なんか最悪計算量が悪くなるの嫌だし、この辺は知識として把握したうえで、なんでもないなら `map`, `set` を使うほうが安定しているのでいい気がする…というのが私の好みではあるんだが Modern C++ における一般的な engineering practice としてはどうなんだろう。
* `unordered_map` の例にこんなのがある
* https://cpprefjp.github.io/reference/unordered_map/unordered_map.html

```cpp
std::unordered_map<std::string, int> um{ {"1st", 1}, {"2nd", 2}, {"3rd", 3}, };
```
* contd.
* 前コメントもらった initializer list というやつだと思うけど、`um {...}` とスペース開けるのが普通では?違うのかな。
* https://cpprefjp.github.io/lang/cpp11/initializer_lists.html
* C++ (だけではないと思うけど)、スペースとかインデントとか改行とか割と無頓着だよな。どっちがいいかはわからないけど。

```cpp
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 {1, 2, 3};

```

* `at` と `[]`
* `at` は範囲外で例外を投げる。
* `[]` は未定義動作
* https://stackoverflow.com/questions/9376049/when-should-i-use-vectorat-instead-of-vectoroperator
* 基本的に `at` を使うほうがいいのかな?
* `map` の存在しないキーにアクセスした場合、指定された型の初期値で初期化されるらしい (`int` なら `0`、ポインタなら `nullptr` などというように)
* ローカル変数の宣言のみの場合は未定義動作になるはずだが、これはどういう違いなんだろう。
* `find` というのもあるみたい
* https://cpprefjp.github.io/reference/map/map/find.html
* 見つからならなかったら `end()` というのを返すらしい
* `std::unordered_map::count`、そもそもキーがユニークなんだから意味ある?と思った。使い所はよくわからんけど (`mulptimap`?あたりとインターフェース揃えるためなのかな)
* `Solution`
* `map` に対する for 文をどう書くのがいいか苦闘した。
* 他の解答も眺めて `for (const auto& [s_char, s_char_count] : s_char_to_count)` で Python の unpack みたいに書けることを知った。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Structured binding で C++17 からで比較的新しい機能です。
なんとなくいつから入ったかは意識しておくといいかと思います。

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.

ありがとうございます。structured binding というんですね。
https://cpprefjp.github.io/lang/cpp17/structured_bindings.html

* const 参照で受けるべきか値で受けるべきかまだよくわからん
* そもそも `map` を範囲 for で回すと pair が返るっぽいので、こんな感じでアクセスする。可読性が低い気がするが慣れの問題?
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Ranged for と auto の入る C++11 以前だとイテレータを型から書いていました。そういう歴史的経緯があります。



```cpp
for (const auto& pair : s_char_to_count) {
if (pair.second != t_char_to_count[pair.first]) {
return false;
}
}
```

* contd.
* `getCountMap` はコピーが発生する可能性を考えると書き込み先を引数でもらった方がいいのかな
* https://github.com/ryosuketc/leetcode_grind75/pull/5#discussion_r2274647457
* *追記:* いや、参照 (かポインタ) を引数に渡せばコピーはされないのか。

### step2

* このバリエーションはいくつもあって、今回は 2 つ map を使ったけどこんな感じでも解けるだろう。
* sort して全部比べる。
* 1 map だけ用意して、最後に全要素が `0` になっているか確認する
* https://github.com/eito2002/LeetCode/pull/2/files
* sort のやり方確認しようと思ったけど、他の要素でお腹いっぱいなのでまた今度。
* `Solution1`
* pair をそのまま使う
* この場合、`pair` という変数名は普通なのかな
* `Solution2`
* find を使う
* この場合、`it` という変数名は普通なのかな
* C++ の場合、`pair` とか `it` みたいな、それ自体はあまり重要じゃないんだけど、みたいな中間変数的なものが発生しがちで、それには短い名前で済ませたくなる。C++ の慣習としてはありなのかな?
* `Solution3`
* getCountMap に書き込み先を渡す
* https://github.com/ryosuketc/leetcode_grind75/pull/5#discussion_r2274647457 を模倣してなんとか書いてみたけどポインタとか参照とか de-reference とか大分わけわからなくなってきた。時間をかけてゆっくり考えるとまあ意味はわかるんだけど。

### step3

* step1 のやり方で、`getCountMap` だけ、`s` を参照で受け取るようにした。コピーは NRVO とか信じてなんとかならないだろうか…
* 追記:
* いや、参照 (かポインタ) を引数に渡せばコピーはされないのか。
* 参照渡しをする、しないによるコピー (引数に渡す時のコピー) と RVO のとき問題になっているコピーを同一視してしまっていた気がする。
* 前者は、ポインタか参照を関数の引数に渡せばよい。
* 後者は値を返すときにコピーされる問題で、嫌なら最初から書き込み先のアドレスを渡して関数内でそこに書いていくか、あるいは (N)RVO が効くことを期待するか
* …という理解であっているかな。
Comment on lines +87 to +92
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.

#3
あたりでポインタと参照を見ていたら勘違いしている気がしてきたので追記

* (N)RVO の理解は浅い気がする
* https://cpprefjp.github.io/lang/cpp17/guaranteed_copy_elision.html
28 changes: 28 additions & 0 deletions 242_valid_anagram/step1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#include <map>

class Solution {
public:
bool isAnagram(string s, string t) {
if (s.size() != t.size()) {
return false;
}
std::map<char, int> s_char_to_count = getCountMap(s);
std::map<char, int> t_char_to_count = getCountMap(t);
for (const auto& [s_char, s_char_count] : s_char_to_count) {
if (s_char_count != t_char_to_count[s_char]) {
return false;
}
}
return true;
}

private:
std::map<char, int> getCountMap(std::string s) {
std::map<char, int> char_to_count;
for (char c : s) {
++char_to_count[c];
}
return char_to_count;
}

};
94 changes: 94 additions & 0 deletions 242_valid_anagram/step2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// pair をそのまま使う
#include <map>

class Solution1 {
public:
bool isAnagram(string s, string t) {
if (s.size() != t.size()) {
return false;
}
std::map<char, int> s_char_to_count = getCountMap(s);
std::map<char, int> t_char_to_count = getCountMap(t);
for (const auto& pair : s_char_to_count) {
if (pair.second != t_char_to_count[pair.first]) {
return false;
}
}
return true;
}

private:
std::map<char, int> getCountMap(std::string s) {
std::map<char, int> char_to_count;
for (char c : s) {
++char_to_count[c];
}
return char_to_count;
}

};



// find を使う
#include <map>

class Solution2 {
public:
bool isAnagram(string s, string t) {
if (s.size() != t.size()) {
return false;
}
std::map<char, int> s_char_to_count = getCountMap(s);
std::map<char, int> t_char_to_count = getCountMap(t);
for (const auto& [s_char, s_count] : s_char_to_count) {
auto it = t_char_to_count.find(s_char);
if (it == t_char_to_count.end() || s_count != it->second) {
return false;
}
}
return true;
}

private:
std::map<char, int> getCountMap(std::string s) {
std::map<char, int> char_to_count;
for (char c : s) {
++char_to_count[c];
}
return char_to_count;
}

};


// getCountMap に書き込み先を渡す
#include <map>

class Solution {
public:
bool isAnagram(string s, string t) {
if (s.size() != t.size()) {
return false;
}
std::map<char, int> s_char_to_count;
getCountMap(s, &s_char_to_count);
std::map<char, int> t_char_to_count;
getCountMap(t, &t_char_to_count);
for (const auto& [s_char, s_count] : s_char_to_count) {
auto it = t_char_to_count.find(s_char);
if (it == t_char_to_count.end() || s_count != it->second) {
return false;
}
}
return true;
}

private:
void getCountMap(std::string& s, std::map<char, int>* to_map) {
for (char c : s) {
++(*to_map)[c];
}
}

};
28 changes: 28 additions & 0 deletions 242_valid_anagram/step3.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#include <map>

class Solution {
public:
bool isAnagram(string s, string t) {
if (s.size() != t.size()) {
return false;
}
std::map<char, int> s_char_to_count = getCountMap(s);
std::map<char, int> t_char_to_count = getCountMap(t);
for (const auto& [s_char, s_char_count] : s_char_to_count) {
if (s_char_count != t_char_to_count[s_char]) {
return false;
}
}
return true;
}

private:
std::map<char, int> getCountMap(const std::string& s) {
std::map<char, int> char_to_count;
for (char c : s) {
++char_to_count[c];
}
return char_to_count;
}

};