Skip to content

Add 349. Intersection of Two Arrays.md #13

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 2 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
223 changes: 223 additions & 0 deletions 349. Intersection of Two Arrays.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
# step 1
考えたこと
- nums1, num2で二重ループ作って、値比較すれば解ける
- num1, num2のどちらかをhashtableに入れればループが一つ減る
- 値->存在するか否かのboolがほしい
- ならsetでいいか
- unique elementだけのリストを返さないといけない
- 一つだけsetを取ると、返り値を保存しているlistのuniqueチェックをどこかでしないといけない
- 両方setをとってintersectionするのが穏当そう

```python
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
set1 = set(nums1)
set2 = set(nums2)
return list(set1 & set2)
```

n, mをnums1.length, num2.lengthとして
- time complexity: O(m + n)
- space complexity: O(m + n)

# step 2
- 一応,`element in list`の挙動を確認した
- https://github.com/python/cpython/blob/30efede33ca1fe32debbae93cc40b0e7e0b133b3/Objects/listobject.c#L619-L636
- 特別なことはなく、端から端まで調べているで良さそう
- set intersectionの挙動
- https://github.com/python/cpython/blob/39e69a7cd54d44c9061db89bb15c460d30fba7a6/Objects/setobject.c#L1354
- [要素数の小さいsetをiterateする様にしていた](https://github.com/python/cpython/blob/39e69a7cd54d44c9061db89bb15c460d30fba7a6/Objects/setobject.c#L1372-L1376)ので、両方setでintersectionを取るときはこの操作自体はO(min(m, n))
- https://github.com/katataku/leetcode/pull/12/files
- 「setに変換したりlistに変換したりするのって意外と抵抗がある」
- 変換にはすべての要素を走査するコストがあるので、その点を理解する必要がある。
- https://github.com/nittoco/leetcode/pull/15#discussion_r1632066961
- 追加の問題として、片方が非常に大きくてもう片方が非常に小さい場合、特に大きいほうがsortされている場合
- 両方sortされている場合
- https://github.com/tarinaihitori/leetcode/pull/13
- step1の様な書き方をして、わざわざsetしたものを変数でおく必要はない。
- setを一つだけ使う(というより、space complexityをO(min(m, n))としたい場合の)解法
- これも片方がものすごく大きい場合どうするかに相当するとおもった。
- https://github.com/hroc135/leetcode/pull/13/files
- pythonでも同様だが、[変数名に型の情報を不必要に入れるべきでない](https://google.github.io/styleguide/pyguide.html#2144-decision:~:text=names%20that%20needlessly%20include%20the%20type%20of%20the%20variable%20(for%20example%3A%20id_to_name_dict))というスタイルがある
- 今回はintersectionだけだと関数名と同じになるし、intersectionで想定するのが集合な気がするが、返り値はintersectionを保持するリストであるので、型名を入れることは許容範囲に感じた
Copy link

Choose a reason for hiding this comment

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

関数名がintersectionであることから積集合を返すことが容易に想像できるので、ローカル変数名はresでもいいかもしれませんね

Copy link

Choose a reason for hiding this comment

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

res は略語ですが1000件ちょっと使われていますね。result のほうが5000件強で多いは多いです。
https://source.chromium.org/search?q=%22return%20res;%22%20filepath:.*%5C.cc$&sq=
https://source.chromium.org/search?q=%22return%20result;%22%20filepath:.*%5C.cc$&sq=

略語は避けるが慣習として明らかならよい、という話でしょう。

略語は略し方の不一致から、たまに気が付きにくい事故を起こします。たとえば、C++ で継承をしようとして Id と ID を間違えると、意図せずに新たに関数を定義してしまうことになります。
これ自体は C++11 からの override つけておきましょう、ですが、英単語を使うことで綴りが違うことへの違和感をより使おうという話です。

- こういう簡単な問題だと特に、問題を見たときに想定する範囲や取り出す知識の幅がまだまだ小さい様に感じた。

片方が非常に大きくてもう片方が非常に小さい場合
m = large_nums.length, n = small_nums.lengthとして、
- time complexity: O(m + n)
- space complexity: O(n)
`small_nums_set.remove(num)`をなくして、ループないで`if num in intersection_list`とすることもできる。が、この場合、intesection_listを全て調べることになる。
```python
class Solution:
def intersection(self, large_nums: List[int], small_nums: List[int]) -> List[int]:
small_nums_set = set(small_nums)
intersection_list = []
for num in large_nums:
if num in small_nums_set:
small_nums_set.remove(num)
intersection_list.append(num)
return intersection_list
```

片方が非常に大きくてもう片方が非常に小さい場合、特に大きいほうがsortされている想定
m = large_nums.length, n = small_nums.length、sort処理を無視するとして、
- time complexity: O(n log m)
- space complexity: O(min(m, n)) = O(n)
```python
class Solution:
def intersection(self, large_nums: List[int], small_nums: List[int]) -> List[int]:
def is_member(sorted_nums: List[int], element: int) -> bool:
left = 0
right = len(sorted_nums)
while left < right:
mid = (left + right) // 2
if sorted_nums[mid] == element:
return True
elif sorted_nums[mid] < element:
left = mid + 1
else:
right = mid
return False
sorted_large_nums = sorted(large_nums)
intersection_list = []
for num in small_nums:
if num in intersection_list:
continue
if is_member(sorted_large_nums, num):
intersection_list.append(num)
return intersection_list
```

両方sortされている想定

sort処理を無視すれば、
- time complexity: O(m + n)
- space complexity: O(min(m, n))
```python
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
sorted_nums1 = sorted(nums1)
sorted_nums2 = sorted(nums2)
index1 = 0
index2 = 0
intersection_set = set()
while index1 < len(sorted_nums1) and index2 < len(sorted_nums2):
if sorted_nums1[index1] == sorted_nums2[index2]:
intersection_set.add(sorted_nums1[index1])
index1 += 1
index2 += 1
Comment on lines +105 to +108
Copy link

Choose a reason for hiding this comment

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

ここのところ、同じものが見つかったら、同じ間ポインターを進めるようにすると、
intersection_set は list でいいですね。
https://discord.com/channels/1084280443945353267/1183683738635346001/1188897668827730010

elif sorted_nums1[index1] < sorted_nums2[index2]:
index1 += 1
else:
index2 += 1
return list(intersection_set)
```

# step 3

```python
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
return list(set(nums1) & set(nums2))
```

# step 4
コメントまとめ
- 略語は避けるが慣習として明らかならよい
- 同じ値がきたら、違う値になるまでindexを進める方法もある
- 人に説明する時にこっちの方が自然な形のように感じた。

- time complexity: O(m + n)
- space complexity: O(m + n)
```python
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
return list(set(nums1) & set(nums2))
```


想定
- どちらも昇順にsortされている
- indexを用意して動かす
- time complexity: O(max(m, n))
- 片方が大きく、片方が小さい(mを大きい方の要素数、nを小さい方とする)
- sortとかはされてない
- 片方をsetに変換する。もう片方をiterate
- time complexity: O(m + n) = O(n)
- space complexity: O(n)
- 大きい方がsortされている
- quick select:
- time complexity: O(n log m)
- space complexity: O(n)
- 小さい方がsortされている
- sortされてない場合と同じように解くとtime complexityは小さくなる

昇順sort済み
```python
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
sorted_nums1 = sorted(nums1)
sorted_nums2 = sorted(nums2)
result = []
index1 = 0
index2 = 0
while index1 < len(sorted_nums1) and index2 < len(sorted_nums2):
if sorted_nums1[index1] < sorted_nums2[index2]:
index1 += 1
continue
if sorted_nums1[index1] > sorted_nums2[index2]:
index2 += 1
continue

duplicated_value = sorted_nums1[index1]
result.append(duplicated_value)
while (
index1 < len(sorted_nums1)
and sorted_nums1[index1] == duplicated_value
):
index1 += 1
while (
index2 < len(sorted_nums2)
and sorted_nums2[index2] == duplicated_value
):
index2 += 1
return result
```

```python
class Solution:
def intersection(self, small_nums: List[int], large_nums: List[int]) -> List[int]:
small_nums_set = set(small_nums)
result = []
for number in large_nums:
if number in small_nums_set:
result.append(number)
small_nums_set.remove(number)
return result
```

```python
class Solution:
def intersection(self, small_nums: List[int], large_nums: List[int]) -> List[int]:
def is_member(number: int, sorted_nums: List[int]) -> bool:
left = 0
right = len(sorted_nums)
while left < right:
mid = (left + right) // 2
if sorted_nums[mid] == number:
return True
if sorted_nums[mid] < number:
left = mid + 1
else:
right = mid
return False

sorted_large_nums = sorted(large_nums)
result = []
for number in small_nums:
if number in result:
continue
if is_member(number, sorted_large_nums):
result.append(number)
return result
```