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
124 changes: 124 additions & 0 deletions 04_HashMap/1.Two_sum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
<問題>
https://leetcode.com/problems/two-sum/

# step1

5分程度答えを見ずに考えて、手が止まるまでやってみる。
何も思いつかなければ、答えを見て解く。
ただし、コードを書くときは答えを見ないこと。
正解したら一旦OK。
思考過程もメモしてみる。

```c++
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
for (int i = 0; i < nums.size() - 1; 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.

私は - 1 しないかもしれないですね。(どうせ空のループが回るだけなので。)

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.

ありがとうございます。
範囲の端はシンプルなほうが読みやすいコードになるので、
今後はそういう観点でもコードを書けるようにしていきます。

for (int j = i + 1; j < nums.size(); j++) {
if (nums[i] + nums[j] == target)
return {i, j};
}
}
return {};
}
};
```

【考えたこと】
- 単純にnC2の組み合わせを全通り考える。
- ソートしてから(O(nlogn))、値iに対して、target-iとなる整数をソート済みの配列にあるか調べる(O(nlogn))方法もありそう。
- 探した要素が元々何番目の要素だったか記憶しておく必要がある。
- 2分探索が必要となる。一度自分で書いてみる。
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.

ありがとうございます!
教えていただいた方法も、2文探索と同じ計算量(logn)で探せて、
解が配列の中で偶然端の方にあると早く探せる感じですね。


# step2
他の方が描いたコードを見て、参考にしてコードを書き直してみる。
参考にしたコードのリンクは貼っておく。
読みやすいことを意識する。
他の解法も考えみる。

計算量:O(N)


```c++
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
map<int, int> num_to_indexes = {};
for (int i = 0; i < size(nums); i++) {
int diffrence = target-nums[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.

- の両隣にスペースを一つずつ空けることをおすすめいたします。

https://google.github.io/styleguide/cppguide.html#Horizontal_Whitespace

Other binary operators usually have spaces around them

if (num_to_indexes.contains(diffrence)) {
return{i,num_to_indexes[diffrence]};
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

{ の前にスペースを一つ空ける。

https://google.github.io/styleguide/cppguide.html#Horizontal_Whitespace

Open braces should always have a space before them.

, の後ろにもスペースを一つ空けたほうがよいと思います。ただしこちらはスタイルガイドに明示的な記述を見つけられませんでした。

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.

丁寧にコードを見ていただきありがとうございます!
少しずつ意識せずにもこういったところをケアできるようにしていきます。

}
num_to_indexes[nums[i]] = i;
}
return {};
}
};
```

例外処理もあると良い。
ここでは、和がtargetとなる組み合わせがない時を想定。
> プログラムの一貫性が重要で、期待する出力が必ずあるべきケースや、異常事態で処理を止めたい場合などは例外発生させる。

> 「公式ドキュメントに目を通す」という行動を取りたくなること自体が大事です。つまり、結果ではなくて欲求を評価しましょう。
何を使うかではなくて、何は不適切であると感じたかも大事です。つまり、結果ではなくて過程を評価しましょう。
最終的にはエンジニアリングという目的との関係から評価します。つまり、結果ではなくて目的を評価しましょう。

C++では例外処理はあまり使わないのか??
一旦、答えがないときは空のペアを返すままとした。
https://google.github.io/styleguide/cppguide.html#Exceptions

mapを使って書いてみる。
空のmapを作って、順番に入れていきペアがあるか知ればれば良い。
keyが与えられた値、valがindex。

mapのcontains()では、keyがあるか探すだけなので、
アクセスによる要素作成はないと認識している。
https://en.cppreference.com/w/cpp/container/map/contains

>今でなくて良いとは思いますが、余裕があればHash Tableの衝突回避のメカニズムも馴染みがなければ軽く目を通しておくと良いと思います。Open Addressing, Separete Chaining


mapは平衡2分木、問題の分類として
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

実際にはRed-Black Treeで実装されることが一般的だそうです。self-balanced binary search treeとしては、他にAVL木などもありますね。self-balancedということで、binary search treeの偏りの問題に対して、回転という操作を加えることでworst caseの深さがO(n)にならないようにするデータ構造です。

https://stackoverflow.com/questions/2558153/what-is-the-underlying-data-structure-of-a-stl-set-in-c
https://en.wikipedia.org/wiki/AVL_tree

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.

教えていただきありがとうございます。
平衡二分探索木は、(最悪ケースの)木の高さと構造維持のための計算量の観点で、様々なアルゴリズムが考え出されているのですね...
赤黒木は回転数が減らせるので、挿入・削除の計算が償却されると0(1)なのですね...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

平衡二分木は一つでいいので聞かれたときに、こういう感じのやつと答えられるようにしておくといいと思います。
以前、私が吐き出してみたときの記述です。
https://discord.com/channels/1084280443945353267/1237649827240742942/1253382452802490431

Hashになっているので、Hashを使った方法もあるはず。
unordered_mapはハッシュデーブルで実装されているらしい。
こちらも使ってよさそう。→
https://cpprefjp.github.io/reference/unordered_map/unordered_map.html

ちなみにsetは二分木
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

setはmapの特殊形として実装されるのが一般的だと思うので、setもRed-Black Treeで実装されることが多いと思います。

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.

すみません、setがmapの特殊形というもは、mapでいうkeyが、
setのvalueで置き換えられたイメージでいいでしょうか...

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だとkey,valueのペアをもたせますが、setだとvalue(あるいはkey)のみをもたせるだけの違いですね。

https://cpprefjp.github.io/reference/set/set.html

unorderedmapとmapの違い
https://qiita.com/taqu/items/110a37df328b6a82c655


- 参考
https://github.com/tarinaihitori/leetcode/pull/11#pullrequestreview-2405412105
https://github.com/takumihara/leetcode/pull/1/files
https://github.com/rihib/leetcode/pull/8/files
https://github.com/colorbox/leetcode/pull/3


# step3

今度は、時間を測りながら、もう一回、書きましょう。書いてアクセプトされたら文字消してもう一回書く。これを10分以内に一回もエラーを出さずに書ける状態になるまで続ける。3回続けてそれができたらその問題はOK。

実施しました。


```c++
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
map<int, int> num_to_indexes = {};
for (int i = 0; i < size(nums); i++) {
int diffrence = target-nums[i];
if (num_to_indexes.contains(diffrence)) {
return{i,num_to_indexes[diffrence]};
}
num_to_indexes[nums[i]] = i;
}
return {};
}
};
```