-
Notifications
You must be signed in to change notification settings - Fork 0
Add 373. Find K Pairs with Smallest Sums.md #10
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,262 @@ | ||
# step 1 | ||
nums1, nums2のインデックスを一つずつ動かす-> wrong answer | ||
全パターン試せていなかった。 | ||
`nums1 = [1, 2, 1000], nums2 = [1, 100], k = 3`でうまくいかなくなる。 | ||
```python | ||
class Solution: | ||
def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]: | ||
if len(nums1) * len(nums2) < k: | ||
raise ValueError( | ||
"k is greater than the number of all possible pairs: " | ||
f"k = {k}, nums1 = {nums1}, nums2 = {nums2}" | ||
) | ||
|
||
k_smallest_pairs = [] | ||
index1 = 0 | ||
index2 = 0 | ||
while len(k_smallest_pairs) < k: | ||
k_smallest_pairs.append([nums1[index1], nums2[index2]]) | ||
|
||
if index1 + 1 == len(nums1): | ||
index2 += 1 | ||
continue | ||
if index2 + 1 == len(nums2): | ||
index1 += 1 | ||
continue | ||
|
||
if nums1[index1 + 1] <= nums2[index2 + 1]: | ||
index1 += 1 | ||
else: | ||
index2 += 1 | ||
return k_smallest_pairs | ||
``` | ||
|
||
leetcodeのeditorialを見て実装. | ||
```python | ||
import heapq | ||
|
||
|
||
class Solution: | ||
def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]: | ||
k_smallest_pairs = [] | ||
visited = set() | ||
next_pair = [] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
||
heapq.heappush(next_pair, (nums1[0] + nums2[0], 0, 0)) | ||
while len(k_smallest_pairs) < k: | ||
_, index1, index2 = heapq.heappop(next_pair) | ||
|
||
if (index1, index2) in visited: | ||
continue | ||
|
||
k_smallest_pairs.append([nums1[index1], nums2[index2]]) | ||
visited.add((index1, index2)) | ||
if index1+1 < len(nums1) and (index1+1, index2) not in visited: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
https://peps.python.org/pep-0008/#whitespace-in-expressions-and-statements
https://google.github.io/styleguide/pyguide.html#36-whitespace
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 同じような処理が 2 度登場するため、関数化して共通化してもよいと思います。 |
||
heapq.heappush( | ||
next_pair, | ||
(nums1[index1+1] + nums2[index2], index1+1, index2) | ||
) | ||
if index2+1 < len(nums2) and (index1, index2+1) not in visited: | ||
heapq.heappush( | ||
next_pair, | ||
(nums1[index1] + nums2[index2+1], index1, index2+1) | ||
) | ||
return k_smallest_pairs | ||
``` | ||
|
||
全てのパターンを試して、小さい順にheapにkだけ詰めて管理することを考えていたが、kが大きくなった時にTLEすると感じた。 | ||
|
||
回答の方は次に探索するインデックスの方をheapで管理していた。グラフ探索みたいだった。 | ||
leetcodeの解答を少し書き換えて、visited.add()を呼び出す場所を自分の感覚に合う様にした。後述したが、これは変数名を`explored`とした方が適切かも。 | ||
その結果として、heapqに同じ探索先が2度入りうることになった(ex. `nums1 = [1, 2, 3], nums2 = [1, 2]`で、next_pairに(4, 1, 1)が2つ入ることになる)。 | ||
|
||
# step 2 | ||
- https://github.com/tarinaihitori/leetcode/pull/10/files | ||
- `num1_i`, `num2_i`と書かれている箇所があり、何度か見間違えた。似た様な意味の変数だと先頭か末尾が違う方が好み。 | ||
- ほぼ同じ解法で`visited`を定義していなかった。 | ||
- 重複なく追加するために、以下の様に書いていた。 | ||
```python | ||
if j == 0 and i + 1 < len(nums1): | ||
heapq.heappush(sum_with_each_index, (nums1[i + 1] + nums2[0], i + 1, 0)) | ||
if j + 1 < len(nums2): | ||
heapq.heappush(sum_with_each_index, (nums1[i] + nums2[j + 1], i, j + 1)) | ||
``` | ||
(i, j)にたどり着くためのルートが一本に定まる様にしてある。 | ||
|
||
個人的には、自分の書いたもののほうが読みやすかった。自分の書いたものは対称的かつ、dijkstraの延長上のものと理解できること、こちらのコードは微妙に対称性が崩れており、その理由を考えるのに時間がかかることが原因だと思う。 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 対称にしないのならば、heapq.merge 使う手もありますかね。 |
||
- `next_pair`変数の代わりに、`sum_with_each_index`という変数名を使っていた。 | ||
- https://github.com/goto-untrapped/Arai60/pull/56 | ||
- heappushする箇所を関数化するのもあり | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. これ、同じようなもので範囲チェックが異なるものが2つある感じがするので、まとめるのはひとつでしょう。 |
||
- leetcodeの解答同様、heapにpushした時点でvisitedとしていた。 | ||
- step1で書いたコードではheappopしたタイミングをvisitedとしていたが、exploredのほうが適切かも。 | ||
- https://github.com/colorbox/leetcode/pull/25/files | ||
- 二分探索を使った解法があった。numの配列が非減少->二分探索と思考を進めるとこういう解法も出てくるのかと面白かった。 | ||
|
||
```python | ||
import heapq | ||
|
||
|
||
class Solution: | ||
def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]: | ||
visited = set() | ||
next_pair_candidates = [] | ||
k_smallest_pairs = [] | ||
|
||
heapq.heappush(next_pair_candidates, (nums1[0]+nums2[0], 0, 0)) | ||
visited.add((0, 0)) | ||
while len(k_smallest_pairs) < k: | ||
_, index1, index2 = heapq.heappop(next_pair_candidates) | ||
k_smallest_pairs.append([nums1[index1], nums2[index2]]) | ||
|
||
if index1 + 1 < len(nums1) and (index1 + 1, index2) not in visited: | ||
heapq.heappush( | ||
next_pair_candidates, | ||
(nums1[index1 + 1] + nums2[index2], index1 + 1, index2) | ||
) | ||
visited.add((index1 + 1, index2)) | ||
if index2 + 1 < len(nums2) and (index1, index2 + 1) not in visited: | ||
heapq.heappush( | ||
next_pair_candidates, | ||
(nums1[index1] + nums2[index2 + 1], index1, index2 + 1) | ||
) | ||
visited.add((index1, index2 + 1)) | ||
return k_smallest_pairs | ||
``` | ||
|
||
# step 3 | ||
|
||
```python | ||
import heapq | ||
|
||
|
||
class Solution: | ||
def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]: | ||
visited = set() | ||
next_pair_candidates = [] | ||
k_smallest_pairs = [] | ||
|
||
heapq.heappush(next_pair_candidates, (nums1[0] + nums2[0], 0, 0)) | ||
visited.add((0, 0)) | ||
while len(k_smallest_pairs) < k: | ||
_, index1, index2 = heapq.heappop(next_pair_candidates) | ||
k_smallest_pairs.append([nums1[index1], nums2[index2]]) | ||
|
||
if index1 + 1 < len(nums1) and (index1 + 1, index2) not in visited: | ||
visited.add((index1 + 1, index2)) | ||
heapq.heappush( | ||
next_pair_candidates, | ||
(nums1[index1 + 1] + nums2[index2], index1 + 1, index2) | ||
) | ||
if index2 + 1 < len(nums2) and (index1, index2 + 1) not in visited: | ||
visited.add((index1, index2 + 1)) | ||
heapq.heappush( | ||
next_pair_candidates, | ||
(nums1[index1] + nums2[index2 + 1], index1, index2 + 1) | ||
) | ||
return k_smallest_pairs | ||
``` | ||
|
||
# step 4 | ||
コメントまとめ | ||
- `+`の両端にスペースを開ける | ||
- visitedはグラフ探索っぽい。今回の文脈ではprocessedのほうが適切 | ||
- 次のノードの決定法で自分の見ていなかった回答がいくつかあった | ||
- index1がある値のときにindex2をどこまで探索したかと、それと逆に、index2がある値のときにindex1をどこまで探索したのかを記録する方法:https://github.com/TORUS0818/leetcode/commit/09636a11c0c084c245f82bb029f198c2d0966455#diff-12341a70069caa7ec44aba610c42a0123605c2680c11a88728158764788bead7R226 | ||
- step2の一番最初のものとほとんど一緒だが、最初にheapqにindex2=0となる全ての場合をpushしてから探索を始めるもの: https://github.com/seal-azarashi/leetcode/pull/10/files/051cdb9294e7cef521f43d05c394e35c40a3d52a | ||
- step2の初めのものよりは読みやすかった。次のノードをpushする際にindex2がひたすらインクリメントされていくだけなので、(i, j)に至るルートは同じだが、わかりやすい。 | ||
|
||
|
||
共通処理の関数化をした解法 | ||
- 初めはnums1, nums2も引数にしていたが、引数の数が大きくなりすぎるのもと思い取り除いた。 | ||
- 引数にない、書き換え可能な値を参照することが気持ち悪い | ||
- pythonだと関数引数にconst指定できないのが少し不便 | ||
- inner functionと例外処理の順番をどうしようか迷った。例外処理してから解法に関係のあるコードを書こうと思い、そう書いた。 | ||
```python | ||
import heapq | ||
|
||
|
||
class Solution: | ||
def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]: | ||
if not 1 <= k <= len(nums1) * len(nums2): | ||
raise ValueError( | ||
'kSmallestPairs(): k is out of range :' | ||
f"nums1 = {nums1}, nums2 = {nums2}, k = {k}" | ||
) | ||
|
||
def add_next_candidate( | ||
next_pairs: List[Tuple[int, Tuple[int, int]]], | ||
index1: int, | ||
index2: int, | ||
processed: Set[Tuple[int, int]] | ||
) -> None: | ||
if ( | ||
index1 < len(nums1) | ||
and index2 < len(nums2) | ||
and (index1, index2) not in processed | ||
): | ||
heapq.heappush( | ||
next_pairs, | ||
(nums1[index1] + nums2[index2], (index1, index2)) | ||
) | ||
|
||
k_smallest_pairs = [] | ||
next_pairs = [] | ||
processed = set() | ||
|
||
heapq.heappush(next_pairs, (nums1[0] + nums2[0], (0, 0))) | ||
while len(k_smallest_pairs) < k: | ||
_, (index1, index2) = heapq.heappop(next_pairs) | ||
if (index1, index2) in processed: | ||
continue | ||
|
||
k_smallest_pairs.append([nums1[index1], nums2[index2]]) | ||
processed.add((index1, index2)) | ||
|
||
add_next_candidate(next_pairs, index1 + 1, index2, processed) | ||
add_next_candidate(next_pairs, index1, index2 + 1, processed) | ||
return k_smallest_pairs | ||
``` | ||
|
||
mergeしていくやり方 | ||
|
||
はじめに(i, 0)のペアを作る。jを1から順にnums2.lengthまで動かしながら、あるjにおける(i, j)のリストを作り(これは非減少)、これまでの最小値のとなるkペアにたいしてmergeする。 | ||
- time complexity: O(k^2) | ||
- space complexity: O(k) | ||
```python3 | ||
import heapq | ||
|
||
|
||
class Solution: | ||
def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]: | ||
k_smallest_pairs = [] | ||
for i in range(len(nums1)): | ||
if len(k_smallest_pairs) == k: | ||
break | ||
k_smallest_pairs.append((nums1[i] + nums2[0], nums1[i], nums2[0])) | ||
|
||
for index2 in range(1, len(nums2)): | ||
if index2 == k: | ||
break | ||
if ( | ||
len(k_smallest_pairs) == k | ||
and k_smallest_pairs[-1][0] <= nums1[0] + nums2[index2] | ||
): | ||
break | ||
smaller_pairs = [] | ||
for index1 in range(len(nums1)): | ||
if ( | ||
len(k_smallest_pairs) == k | ||
and k_smallest_pairs[-1][0] <= nums1[index1] + nums2[index2] | ||
): | ||
break | ||
if len(smaller_pairs) == k: | ||
break | ||
smaller_pairs.append(( | ||
nums1[index1] + nums2[index2], | ||
nums1[index1], | ||
nums2[index2] | ||
)) | ||
k_smallest_pairs = list(heapq.merge(k_smallest_pairs, smaller_pairs))[:k] | ||
|
||
return [[num1, num2] for _, num1, num2 in k_smallest_pairs] | ||
``` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 訪問済み(処理済み)の座標をO(2n)の空間計算量で管理する方法もあります |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
visited という変数名は、グラフ等ですでに探索済みのノードを表すときに見かけますが、今回のように処理済みであることを表すにはやや不適切なように思います。 proceeded または proceeded_index_pairs はいかがでしょうか?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
コメントありがとうございます。
グラフ探索っぽい感覚があったほうがコード全体の理解が早いかなくらいの気持ちで名付けてました。確かに、状況的に
proceeded
とかの方がわかりやすいですね。There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(processed かしら。)