diff --git a/1.Two_sum.md b/1.Two_sum.md new file mode 100644 index 0000000..b6215a0 --- /dev/null +++ b/1.Two_sum.md @@ -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 + +class Solution { + public: + std::vector twoSum(std::vector& 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 +#include + +class Solution { + public: + std::vector twoSum(std::vector& nums, int target) { + std::map 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()) { + return {map_to_complement_index[complement], i}; + } + map_to_complement_index[nums[i]] = i; + } + + return {}; + } +}; +``` +no.3 +```c++ +#include +#include + +class Solution { + public: + std::vector twoSum(std::vector& nums, int target) { + std::set 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}; + } + numSet.insert(nums[i]); + } + + return {}; + } +}; +``` + + +## 思考過程 +- 問題だけ読んだ時は、愚直な解答(no.1)はすぐ書けると考えました。 + +- 何回かこの問題のレビューをみていたので、mapを使う方法もあると朧げながら覚えていた。unordered_map, mapなどの使い方をAIとリファレンスを言ったり来たりしてno.2は書きました。 +- 途中でmapの特徴などを見ていたら、set(集合)的なやつでも書けるのでは?と思いsetのリファレンスも見に行きました。 + →しかしsetは`set`であるため値を放り込んでおくことしかできないと気づく。そうなると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 {};が返されたときってどう検知しようかと一瞬迷ったので自然な返り値は大事。 +```c++ +std::vector 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をとる場合などに)同じリストに配属されてしまうみたいなことがなければ、かなり早いものの、データによっては(最悪の場合)線形探索と同等になってしまうイメージ。要求とデータが上手い具合に重なれば選択肢となりそう。 +- 一方で赤黒木はあまり詳しい実装方法は知らないが、平衡二分木の一つという話は知ってて、平衡二分木はきれいな二分木にするために毎回深さや層を調整する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 +#include + +class solution { + public: + std::vector twoSum(std::vector& nums, int target) { + std::map 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回書くと流石にすらすらかけるようになった。 + +```c++ +#include +#include + +class solution { + public: + std:vector twoSum(std::vector& nums, int target) { + std::map 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 {}; + } +} +``` +