Skip to content

Add 1. Two Sum.md #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
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
130 changes: 130 additions & 0 deletions 1. Two Sum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# step 1
解くこと自体は簡単だった。

与えられた数値を持つindexを`num_to_index`にいれて記録したが、同じ値があった場合、後からでた
もののindexしか記録しない様になっている。全ての組み合わせを出せとかだとうまくいかない。

条件を満たす組がちょうど一つしか存在しないという仮定があったためこの様にした。この仮定のもとで
わざわざ`num_to_indices`とかにするのも不必要に複雑かと判断した。

条件を満たす組がない場合、Noneを返すか、例外を発生させるか迷った。
type annotation的に後者のほうがいいと判断した。

```python
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
num_to_index = dict()
for i in range(len(nums)):
rest = target - nums[i]
if rest in num_to_index:
return [num_to_index[rest], i]

num_to_index[nums[i]] = i

raise RuntimeError(
f"No pair found in twoSum(): nums = {nums}, target = {target}"
)
```
# step 2
- https://github.com/katataku/leetcode/pull/10/files
- 入力をsortして、両端から調べていく解法もある
- `for i, num in enumerate(nums):`使っていた。`range(len(nums))`よりも簡潔(関数呼び出しの数が少なく)でよさそう
- https://github.com/haniwachann/leetcode/pull/2
- `for (int i = 0; i < nums.size() - 1; i++)`で-1しない考えもある。空のループが回るだけだから。
- ボトルネックでないところは、最適化するなを思い出した。
- https://github.com/philip82148/leetcode-arai60/pull/1
- 実際にsortしている手法

sortして端から抑えていく

`n = nums.length`として、
- time complexity: O(n log(n))
- space complexity: O(n)
```python
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
num_index_pairs = []
for i, num in enumerate(nums):
num_index_pairs.append((num, i))
num_index_pairs.sort()

left = 0
right = len(nums) - 1
while left < right:
two_sum = num_index_pairs[left][0] + num_index_pairs[right][0]
if two_sum == target:
return [num_index_pairs[left][1], num_index_pairs[right][1]]

if two_sum > target:
right -= 1
else:
left += 1

raise RuntimeError(
f"No pair found in twoSum(): nums = {nums}, target = {target}"
)
```

hash tableを使った解法
`n = nums.length`として、
- time complexity: O(n)
- space complexity: O(n)
```python
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
num_to_index = dict()
for i, num in enumerate(nums):
rest = target - num
if rest in num_to_index:
return [num_to_index[rest], i]

num_to_index[num] = i

raise RuntimeError(
f"twoSum(): No pair found: nums = {nums}, target = {target}"
)
```

# step 3

```python
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
num_to_index = dict()

Choose a reason for hiding this comment

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

{}でもdictの初期化はできますね。(こちらは構文レベルでサポートされており、dict関数の呼び出しが分、ちょっとパフォーマンス的に良かったりもします。)

Copy link
Owner Author

Choose a reason for hiding this comment

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

勝手に{}dict()のsyntax sugerだと思ってました。
パフォーマンスの違い知りませんでした。
ありがとうございます。

for i, num in enumerate(nums):
rest_value = target - num
if rest_value in num_to_index:
return [num_to_index[rest_value], i]

num_to_index[num] = i
raise RuntimeError(

Choose a reason for hiding this comment

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

RuntimeErrorは適していないような気がします。ドキュメント的には他に当てはまるものがないときの最終手段みたいな感じでしょうか

https://docs.python.org/3/library/exceptions.html#RuntimeError

Raised when an error is detected that doesn’t fall in any of the other categories. The associated value is a string indicating what precisely went wrong.

Choose a reason for hiding this comment

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

builtin errorを使うならValueErrorですかね
https://docs.python.org/3/library/exceptions.html#ValueError

Copy link

Choose a reason for hiding this comment

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

私も ValueError のほうかなあと思いました。
ただ、大事なのは、標準ドキュメントを見て考えたかどうか、かと思います。

Copy link
Owner Author

Choose a reason for hiding this comment

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

コメントありがとうございます。

ValueErrorは、関数冒頭で行える様な引数チェックで引っかかる、引数が見るからに適切でない値の時(たとえば今回の問題だと、引数にくるnumsの要素数が1の時)に発生させるイメージでした。

頑張って探した(探せた)けど、求めているものが見つからない場合は、ValueErrorには当たらないのかなぁという判断でした(今回は要素数1もここに入ってしまいますが)。

ValueError: Raised when an operation or function receives an argument that has the right type but an inappropriate value, and the situation is not described by a more precise exception such as IndexError.

とあるのでinappropriateの解釈が、ちょっと不適切だったかもしれません。

Choose a reason for hiding this comment

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

すみません、ちょっと関連して私見を述べたくなったのでコメントさせていただきます
(この認識が違っていたら講師役の方に訂正を願いたいです)

こういったもの(エラー以外の例はあんまり思いつきませんが)の方針は、出来るだけより詳細を示せるエラーの型を選ぶとよいと僕は思っています。
なぜなら、エラー文が人間が理解しやすいもの、エラーの型等はコンピューターが理解しやすいもの、といった認識があるからです。
RuntimeErrorにしてしまうと、もしこの関数の中で他にRuntimeErrorをraiseする関数があったら、コンピューター(この関数のエラーをキャッチする呼び出し元)にとってはエラー文を読むことでしか区別が出来ません。
でも、ValueErrorやよりシチュエーションを正確に表すエラーの型なら、他と被る確率を下げることが出来ます。

※また、この関数でRuntimeErrorを2か所以上raiseしなければいいというわけでもなく、この関数と一緒に他のRuntimeErrorをraiseする関数と一緒にtry-catchブロックに入れることでも同じ問題が起きます。

究極、(言い過ぎかもしれませんが)型は人間の認識と少しぐらいずれていてもよく、人間にとってはエラー文の方が本命だと思っています。

Copy link
Owner Author

Choose a reason for hiding this comment

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

try-catchブロックの中で細かい粒度で例外を捉えられるようにという意識があまりなかったです。勉強になります。

f"twoSum(): No pair found: nums = {nums}, target = {target}"
)
```

# step 4
コメントまとめ
- このシチュエーションならRuntimeErrorかなとおもったが、odaさん・hayashiさんはValueErrorを挙げていた。自分の感覚が少しずれていそうだった。
- RuntimeError: Raised when an error is detected that doesn’t fall in any of the other categories. The associated value is a string indicating what precisely went wrong.
- ValueError: Raised when an operation or function receives an argument that has the right type but an inappropriate value, and the situation is not described by a more precise exception such as IndexError.
- RuntimeErrorは相当強い条件の中で使うもので、ValueErrorにおける、an inappropriate valueの解釈は自分の想定していたより広くとって良さそう
- dict()と{}が異なる。
- https://pylint.readthedocs.io/en/latest/user_guide/messages/refactor/use-dict-literal.html
- https://madebyme.today/blog/python-dict-vs-curly-brackets/
- わかりやすくていい記事だった。

restからcomplementに変更した。
```python
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
num_to_index = {}
for i, num in enumerate(nums):
complement = target - num
if complement in num_to_index:
return [num_to_index[complement], i]
num_to_index[num] = i
raise ValueError(
"twoSum(): No pair found: "
f"nums = {nums}, target = {target}"
)
```