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
54 changes: 54 additions & 0 deletions 383_ransom_note/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# 383. Ransom Note

https://leetcode.com/problems/ransom-note/

## Comments

### step1

* 最初、「重複を許すset」みたいなデータ構造(検索や削除はO(1))があればいいのにな、と思った
* step2 で追記。`std::multiset`, `std::unordered_multiset`
* あったような気がするがぱっとわからなかったので map で実装してみた (unordered_map でもよかったが、平均計算量のよい map にしてみた。そこまで深い考察はない)。
Copy link

Choose a reason for hiding this comment

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

平均計算量は unordered_map のほうが良いですが、一部の操作については map のほうが早い、だと思います。

https://chromium.googlesource.com/chromium/src/+/HEAD/base/containers/README.md#std_unordered_map-and-std_unordered_set

In a microbenchmark on Windows, inserts of 1M integers into a std::unordered_set took 1.07x the time of std::set, and queries took 0.67x the time of std::set.

* しばらく間が空いたので範囲 for とか一瞬書き方を忘れていた。
* 5:00 くらいで書いてみた

### step2

* int の map に `[]` でアクセスするとき、初期値は 0 になるので、`if (!char_count.contains(c) || char_count[c] == 0)` は単に `if (char_count[c] == 0)` でよかった気はする
* `[]` アクセスとか、`at` とかまだちょっと苦手
* `[]`
* 範囲外アクセスをチェックしない。map だと新しい要素が作られる
* `at`
* 範囲外アクセスで例外を投げる。map ではキーが存在しなければ例外を投げる
* 基本的には `at` の方が安全、`[]` の方がチェックしない分ちょっとだけ高速

* `std::multiset`
* https://cpprefjp.github.io/reference/set/multiset.html
* `std::unordered_multiset`
* https://cpprefjp.github.io/reference/unordered_set/unordered_multiset.html

* `multiset` 使ってみた。
* `Solution1WA`: 最初、`erase` が 1 個だけ要素を削除すると思っていたが、キーに合致するものは全部削除するらしい
* `Solution1AC`: 正しくは iterator を使うみたい。`multiset` だときれいに (`Solution1WA` のように) 書ける想像をしていたが、これなら別に map でもいいかなという感じ
* ちみに `std::multiset.find` は O(logn)、`std::unordered_multiset.find` は avg. O(1), worst O(n)
* まあ内部実装 (binary tree vs. hash) を考えれば当然
* https://cpprefjp.github.io/reference/set/multiset/find.html
* https://cpprefjp.github.io/reference/unordered_set/unordered_multiset/find.html
* `Solution2`
* > ransomNote and magazine consist of lowercase English letters.
* この制約見逃していた。これあるなら 26 文字なので、長さ 26 vector を用意して ++, -- していっても解ける
* vector 使ったが可変長配列である必要はないかもしれない。
* 一応サイズと初期値を与えられるらしい。
* `std::vector<int> char_count(26, 0);` みたいに、`variable_name(args)` という書き方でコンストラクタを呼ぶの、なんかちょっと慣れない。
* Python なら `variable_name = ClassName(args)` とか
* C++ でも `std::vector<int> char_count = std::vector<int>(26, 0);` と書くことはできる。一旦オブジェクト作ってから変数に束縛するので無駄な処理がありそうだけど。
* vector の初期化といえば initializer list とかもある。まあこれはまた違う話。
* https://cpprefjp.github.io/lang/cpp11/initializer_lists.html
* `insert` vs. `emplace`
* `emplace` の「要素を直接つくる」とは何かと思ったが、そもそも `insert` はコンテナ外で一時オブジェクトを作ってそれをムーブしているらしい
* なので基本的には `emplace` (C++11)の方が効率が良く、そちらを使う
Copy link

Choose a reason for hiding this comment

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

Chromium と Google は、基本 insert を使えみたいですね。
https://groups.google.com/a/chromium.org/g/chromium-dev/c/7mJypsYz6AA/m/reAeaonzBQAJ
https://abseil.io/tips/112

「効率が良く」に対して、「それは何秒くらい速くなるのか、本当にディスアドバンテージはないのか」とまず感じます。「速度」はエンジニアリングをする上でかなり弱い根拠なんですね。

* TODO: ムーブといえば、社内の詳しい人は move semantics について苦笑いしていた。あれが登場したときは色々あったらしい。そもそも move semantics の話よくわかっていないのでそのうちに調べる

### step3

* いくつかパターン書いたので今回は省略
16 changes: 16 additions & 0 deletions 383_ransom_note/step1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
std::map<char> char_count;
Copy link

Choose a reason for hiding this comment

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

c++にあまり詳しくないのですが、こちらvalueの型は指定しなくてよいのでしょうか?
leetcode上で試してみるとcompile errorとなりました

for (char c : magazine) {
++char_count[c];
}
for (char c : ransomNote) {
if (!char_count.contains(c) || char_count[c] == 0) {
Copy link

Choose a reason for hiding this comment

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

if (--char_count[c] < 0) {
    return false;
}

のほうがシンプルだと思います。

return false;
}
--char_count[c];
}
return true;
}
};
58 changes: 58 additions & 0 deletions 383_ransom_note/step2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
// WA - `erase` はそのキーの全ての要素を削除する

class Solution1WA {
public:
bool canConstruct(string ransomNote, string magazine) {
std::multiset<char> char_count;
for (char c : magazine) {
char_count.insert(c);
}
for (char c : ransomNote) {
if (!char_count.contains(c)) {
return false;
}
char_count.erase(c);
}
return true;
}
};


class Solution1AC {
public:
bool canConstruct(string ransomNote, string magazine) {
std::multiset<char> char_count;
for (char c : magazine) {
char_count.insert(c);
}
for (char c : ransomNote) {
if (!char_count.contains(c)) {
return false;
}
Comment on lines +29 to +31
Copy link

Choose a reason for hiding this comment

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

この 3 行は消せると思います。

auto it = char_count.find(c);
if (it == char_count.end()) {
return false; // no match found
}
char_count.erase(it);
}
return true;
}
};

class Solution2 {
public:
bool canConstruct(string ransomNote, string magazine) {
std::vector<int> char_count(26, 0);
for (char c : magazine) {
++char_count[c - 'a'];
}
for (char c : ransomNote) {
if (char_count[c - 'a'] == 0) {
return false;
}
--char_count[c - 'a'];
}
return true;

}
};
Empty file added 383_ransom_note/step3.cpp
Empty file.