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
102 changes: 102 additions & 0 deletions 1. Two Sum/TwoSum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
# step1 何も見ずに解く

## 解答
- hashMapを使った実装
- partnerという単語がパッと思い浮かんだが分かりやすいだろうか

Choose a reason for hiding this comment

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

partnerはちょっと違和感ありますね。

a person or organization you are closely involved with in some way

https://dictionary.cambridge.org/dictionary/english/partner

Copy link
Owner Author

Choose a reason for hiding this comment

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

やはり自然な英単語としてはcomplementになるでしょうか。
コメントありがとうございます!

Choose a reason for hiding this comment

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

まあ無理に1単語で表さなくても、「足すとtargetになる数字」、「targetとの差分」みたいな感じでdifferenceToTargetとかでも良いかなと思いました。

Copy link
Owner Author

Choose a reason for hiding this comment

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

良い変数名を例示いただきありがとうございます!
complementより直感的に分かりやすいです。

- `numToIndex.get()`した値をnullチェックするより、`numToIndex.containsKey()`する方が読み手にとって親切だろうか
- hashの計算を2回するのがもったいないように感じて最初から`get()`する形で書いた
Comment on lines +6 to +7
Copy link

Choose a reason for hiding this comment

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

大前提として場合によりけりだとは思いますが、個人的には計算時間の最適化と可読性のトレードオフになった場合は可読性を優先したほうがよいのではないかと考えます。
ドナルド・クヌースは早すぎる最適化は諸悪の根源であると述べたようです。
またパフォーマンスチューニングにおいては「推測するな、計測せよ」という有名な格言もあります。

もしパフォーマンスを気にされるのであればベンチマークを取るなどの計測を行った上で読みやすさより速度を優先した、という決定をされるのがよいのかなと思います。

Copy link
Owner Author

Choose a reason for hiding this comment

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

仰る通りです。また現実問題、ここで時間をケチったところで問題が解決することは無さそうなのでちょっと余計でした。

- 計算量
- 時間計算量: O(n)
- 空間計算量: O(n)
```java
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> numToIndex = new HashMap<>();
for(int i = 0; i < nums.length; i++) {
Copy link

Choose a reason for hiding this comment

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

for のあとにスペースを空けることをおすすめします。

参考までにスタイルガイドへのリンクを貼ります。

https://google.github.io/styleguide/javaguide.html#s4.6.2-horizontal-whitespace

Separating any keyword, such as if, for or catch, from an open parenthesis (() that follows it on that line

上記のスタイルガイドは唯一絶対のルールではなく、複数あるスタイルガイドの一つに過ぎないということを念頭に置くことをお勧めします。また、所属するチームにより何が良いとされているかは変わります。自分の中で良い書き方の基準を持ちつつ、チームの平均的な書き方で書くことをお勧めいたします。

int num = nums[i];
int partnerNum = target - num;
Integer partnerIndex = numToIndex.get(partnerNum);
Copy link

Choose a reason for hiding this comment

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

自分なら int で受けるのですが、好みの問題かもしれません。

Copy link
Owner Author

Choose a reason for hiding this comment

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

確かに後続の処理では int としてしか使っていないので、この時点で int としておくべきでした。

if (partnerIndex != null) {
return new int[]{partnerIndex, i};
}
numToIndex.put(num, i);
}
return new int[]{};
}
}
```
- 二重にforループを使った総当たり実装
- 最初に書いた時、ループのindexが重なっている時に`continue`するのを忘れていた
- 計算量
- 時間計算量: O(n^2)
- 空間計算量: O(1)
- よく考えたら空間計算量的には二重ループの方が優れているか
- 要素数が一定より少なければhashMapの生成などが不要なためこちらの方が高速になるだろう
```java
class Solution {
public int[] twoSum(int[] nums, int target) {
for(int i = 0; i < nums.length; i++) {
for(int j = 0; j < nums.length; j++) {
if (i == j) {
continue;
}
if (target == nums[i] + nums[j]) {
return new int[]{i, j};
}
}
}
return new int[]{};
}
}
```
# step2 他の方の解答を見る
## 解答
- https://github.com/akmhmgc/arai60/pull/8/files
- よく考えたら内側のループの始点は`i + 1`でよかった
- それ以前の値は確認済みのため確認不要
- また該当がなかった場合は仮の値を何かしら入れておくのが良いだろう
- 到達することは無いはずだから適当でいいかくらいにstep1では考えてた
- nullか-1を入れておくのが一般的だろうか
```java
class Solution {
public int[] twoSum(int[] nums, int target) {
for (int i = 0; i < nums.length; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (target == nums[i] + nums[j]) {
return new int[]{i, j};
}
}
}

Choose a reason for hiding this comment

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

細かいところで恐縮ですが、ここの閉じカッコ"}"と対応するfor文だけインデントがズレてませんか?

Copy link
Owner Author

Choose a reason for hiding this comment

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

本当ですね。ありがとうございます。

// ここに到達することは無いはず
return new int[]{-1, -1};
}
}
```
- hashMapを使った実装において処理の流れは一緒だが以下のような書き方も想定できたらよかった
- ターゲットとある値の差分を`complement`とする
- Mapのキーとして「走査済みの値」ではなく「ターゲットと走査した値の差分」をセットする
Copy link

Choose a reason for hiding this comment

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

確かにそのような解き方もありますね。勉強になりました。

- 後で引き算するか先に引き算するかの違い。個人的には今回書いている前者の方が分かりやすいように思う
- その他二分探索を使用した書き方も考えられる
- しかし二分探索するためにはソート&元のインデックスを保持する必要があるためこの場合は不適なように感じる
- この問題の条件で二分探索の方が適する入力のケースってあるだろうか?
Copy link

Choose a reason for hiding this comment

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

メモリーに片方が乗らないサイズとかですかね。

まあ、私が思うのは、この問題、手で解くとしたらどうするかです。部屋にカードが1000枚あって、和がある値になる組を探せといわれたら、100万回足し算するかですよ。

# step3 3回ミスなく書く
- 所要時間: 4分程度
## 解答
- HashMapを使った実装
```java
class Solution {
public int[] twoSum(int[] nums, int target) {
Map<Integer, Integer> numToIndex = new HashMap<>();
for(int i = 0; i < nums.length; i++) {
int num = nums[i];
int partnerNum = target - num;
Copy link

Choose a reason for hiding this comment

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

分かりづらいわけではないですが、個人的には上で述べられているcomplementなどのほうがより分かりやすい気がしました。

Integer partnerIndex = numToIndex.get(partnerNum);
if (partnerIndex != null) {
return new int[]{partnerIndex, i};
}
numToIndex.put(num, i);
}
return new int[]{-1, -1};
}
}
```