Skip to content
Open
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
159 changes: 159 additions & 0 deletions 1.Two_sum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
URL:https://leetcode.com/problems/two-sum/

Arai 1 / 60問目

# Step1

- 使用言語:c++(普段はC言語などが主なためほぼC++初心者です)
- 所要時間:骨格部分は2~5分(STLの使い方を調べるのにはもっとかかりました)

no.1
```c++
#include <vector>

class Solution {
public:
std::vector<int> twoSum(std::vector<int>& nums, int target) {
for (int i = 0; i < nums.size(); i++) {
for (int j = i + 1; j < nums.size(); j++) {
if (nums[i] + nums[j] == target) {
return {i, j};
}
}
}
return {};
}
};
```
no.2
```c++
#include <vector>
#include <map>

class Solution {
public:
std::vector<int> twoSum(std::vector<int>& nums, int target) {
std::map<int, int> map_to_complement_index;

for (int i = 0; i < nums.size(); ++i) {
int complement = target - nums[i];
if (map_to_complement_index.find(complement) != map_to_complement_index.end()) {

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 (map_to_complement_index.containts(complement)) {

としたほうがシンプルだと思います。あるいは、 2 度検索が走らないよう、

if (auto it = map_to_complement_index.find(complement); it != map_to_complement_index.end()) {
  return {it->second, i};
}

とするのもよいと思います。

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.

containsは自分のすぐあとに解いた方が書いていて、見逃してた!となりました。
auto it = map_to_complement_index.find(complement); it != map_to_complement_index.end()もfindで見つけてきたものをうまく使っていてこれならfind使った意図がわかる感じですね
コメントありがとうございます!

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.

Hurukawa2121/leetcode#6 (comment)
調べなおしているときに出てきた話。findがありゃ十分だろというC++03以前の考えもわかるけど今回みたいなあるか否かで条件式に入れたい場合はcontainsやっぱり便利なのでC++20以降なら使っていきたい。

return {map_to_complement_index[complement], i};
}
map_to_complement_index[nums[i]] = i;
}

return {};
}
};
```
no.3
```c++
#include <vector>
#include <set>

class Solution {
public:
std::vector<int> twoSum(std::vector<int>& nums, int target) {
std::set<int> numSet;

for (int i = 0; i < nums.size(); ++i) {
int complement = target - nums[i];
if (numSet.find(complement) != numSet.end()) {
int complementIndex = std::distance(nums.begin(), std::find(nums.begin(), nums.end(), complement));
return {complementIndex, 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.

変数名は lower_snake で書くのをよく見かけます。チームの平均的な書き方に合わせることをお勧めいたします。

参考までに Google C++ Style Guide へのリンクを貼ります。
https://google.github.io/styleguide/cppguide.html#Variable_Names

The names of variables (including function parameters) and data members are snake_case (all lowercase, with underscores between words).

ただし、上記のスタイルガイドは唯一絶対のルールではなく、複数あるスタイルガイドの一つに過ぎないということを念頭に置くことをお勧めします。

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.

変数名のスタイルはいくつかありますが、統一してなかったのが痛手ですね。

}
numSet.insert(nums[i]);
}

return {};
}
};
```


## 思考過程
- 問題だけ読んだ時は、愚直な解答(no.1)はすぐ書けると考えました。

- 何回かこの問題のレビューをみていたので、mapを使う方法もあると朧げながら覚えていた。unordered_map, mapなどの使い方をAIとリファレンスを言ったり来たりしてno.2は書きました。
- 途中でmapの特徴などを見ていたら、set(集合)的なやつでも書けるのでは?と思いsetのリファレンスも見に行きました。
→しかしsetは`set<T>`であるため値を放り込んでおくことしかできないと気づく。そうなるとcomplementは見つけられてもそこからインデックスを抽出するのにそのcomplementを探す線形探索が必要になる。これはAIに書いてもらった(no.3)

- ここら辺で問題を再整理。ほしいのは足すとtargetになるインデックスのペアなので、値をkey、インデックスをvalueにする形で調べた値は保管しておいて、ペアになる値があるか探すno.2はかなり自然に感じる。
- setの回答は途中までいい感じで進めてるけど、いざペアが見つかった!となったあとにこの値って何番目だっけ?とインデックスを探しに行っている感じがちょっと素っ頓狂に見える。

- あとはこのtwoSum関数がどこまで使われて、かつ速さが求められるか、チームのメンバのC++の理解などでどの方式にするかは違うかも。特に自分はまだC++に慣れてないため、no.2が理解したら自然に感じられるとはいえど、no.1の方が理解しやすい。ただ、最近C言語で配列でのループをみる時も、2重ループ程度でもえーっとこのi,jは初期値が何でどうゆう条件でループするんだっけと意外とワーキングメモリ使っていて読むの大変だなと思うときがある。多分慣れるとno.2の方がわかりやすいんだろうなと思う。

# step2

https://github.com/Miyamoto-tryk/leetcode-arai60/pull/1/files
https://github.com/Shinkomori19/arai60/pull/2/files
https://github.com/azriel1rf/leetcode-prep/pull/3
- 他の方の実装を読む。見つからなかったときにどういう風に返すのが自然か。実際Step1を実際に手元で確かめることをしてた時、これreturn {};が返されたときってどう検知しようかと一瞬迷ったので自然な返り値は大事。

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
Owner Author

Choose a reason for hiding this comment

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

なるほど、大まかな枠の把握みたいなことですね?たしかに大まかな方針がたっても意外と細かい条件への対処で見通しとずれることはあるので意識したいと思います!

```c++
std::vector<int> result = solution.twoSum(nums, target);
if (result.size() != 2) {
std::cout << "No solution found." << std::endl;
return 1;
}
```
- pythonなどだとNoneを返す手がある。AIに聞いたらC++17から使えるようになったstd::optionalを使うとちょっと返り値の部分をいじる必要はあるもののstd::nulloptというnullっぽい値を返すことができるようだ。これはC言語でNULLを返しがちな自分としては好み。
- {-1, -1}というようにこの問題ではあり得ない値のペアを返す案も見かけたが、今後データがマイナスまで伸びるかもしれないとか考えると使いづらいと思いました。
- exceptionをthrowするのは、ちょっとこの関数を使うためだけに呼び出し元でtry catch書かせるのは違うかなという感覚があります。


- C++だとunordered_mapかmapかという話がよく出てくる。自分はリファレンスを調べたときにunordered_mapがハッシュテーブル、mapが赤黒木という実装だと読んだ。自分の中でハッシュテーブルは(たとえばmodをとる場合などに)同じリストに配属されてしまうみたいなことがなければ、かなり早いものの、データによっては(最悪の場合)線形探索と同等になってしまうイメージ。要求とデータが上手い具合に重なれば選択肢となりそう。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

自分はリファレンスを調べたときにunordered_mapがハッシュテーブル、mapが赤黒木という実装だと読んだ。

C++ の規格では、時間計算量だけが規定されており、データ構造については言及がありません。

https://timsong-cpp.github.io/cppwp/n4950/associative.reqmts#general-65

Complexity: Logarithmic.

https://timsong-cpp.github.io/cppwp/n4950/unord.req#general-97

Complexity: Average case O(1), worst case O(a_uniq.size()).

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

(実際の実装は確かにハッシュテーブルと赤黒木であることが多いです。)

@YUTA-Uchi YUTA-Uchi May 9, 2025

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.

おそらくdiscordなどの情報とごっちゃになっていて日本語リファレンスを読んだときにそう読み取ってしまったようです。しっかり規格なども裏どりしていきたいです!

後からきてこの辺が気になった方へ(英語版のmapのリファレンスにはred-black treeへの言及があります。「一般的には赤黒木で実装されることが多い」)
https://en.cppreference.com/w/cpp/container/map
https://en.cppreference.com/w/cpp/container/unordered_map

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 と unordered_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.

ありがとうございます!探してみます!

- 一方で赤黒木はあまり詳しい実装方法は知らないが、平衡二分木の一つという話は知ってて、平衡二分木はきれいな二分木にするために毎回深さや層を調整するtreeだと理解していました。こちらのほうが平均的にそちらを選びました。

https://github.com/irohafternoon/LeetCode/pull/13/files
- map_to_complement_indexという変数名は、to_complement_indexの部分はなるべく分かりやすいようにしたつもりだったがmap型の変数にmapとつけるのは冗長だと反省。num_to_index, num_to_indexes, num_to_indicesなどが多い。indexを複数形にするかは悩みどころ。
- 一般的に複数形の方が有標なので、複数形にしていると何か意図があるのかもと考えてしまうかも。今回のようにすぐに中身がわかる(スコープがそんなに広くない)変数であれば、まあどっちでもいいかもですがnum_to_indexesなどだと一瞬1対多なのかと思ったり。

```c++
#include <vector>
#include <map>

class solution {
public:
std::vector<int> twoSum(std::vector<int>& nums, int target) {
std::map<int, int> num_to_index;

for (int i = 0; i < nums.size(); i++) {
int complement = target - nums[i];
if (num_to_index.find(complement) != num_to_index.end()) {
return {num_to_index[complement], i};
}
num_to_index[nums[i]] = i;
}

return {};
}
}
```

step3
- 3回書くと流石にすらすらかけるようになった。

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
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます!合間を見つけてどんどんやっていきたいです!


```c++
#include <vector>
#include <map>
Comment on lines +138 to +139

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ここはアルファベティカルに並べることもありますね。
標準ライブラリー -> サードパーティーライブラリー -> 自作ライブラリー -> .cc と同名のヘッダーファイル
という順番が多いです。

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.

あ、なるほど。アルファべティカルの方が検索性がよいですね(特にincludeが増えた場合)
標準ライブラリー -> サードパーティーライブラリー -> 自作ライブラリー -> .cc と同名のヘッダーファイル
という順番は自作ライブラリで標準ライブラリやサードパーティーライブラリで定義されている定義されている定数やマクロとのダブりがコンパイル段階でわかりそうで今後意識します。


class solution {
public:
std:vector<int> twoSum(std::vector<int>& nums, int target) {
std::map<int, int> num_to_index;

for (int i = 0; i < nums.size(); i++) {
int complement = target - nums[i];

if (num_to_index.find(complement) != num_to_index.end()) {
return {num_to_index[complement], i};
}
num_to_index[nums[i]] = i;
}

return {};
}
}
```