-
Notifications
You must be signed in to change notification settings - Fork 0
373.Find K pairs with smallest sums #33
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,182 @@ | ||
| ### Step1 | ||
|
|
||
| - どっかで見たことあったので解けた | ||
| - 最初、すでに突っ込んだペアかのチェックを忘れてた | ||
|
|
||
| ```python | ||
|
|
||
| class Solution: | ||
| def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]: | ||
| num_pairs = 0 | ||
| pairs_heap = [(nums1[0] + nums2[0], 0, 0)] | ||
| result_pairs = [] | ||
| checked = set((0, 0)) | ||
| while len(result_pairs) < k: | ||
| pair_sum, index1, index2 = heappop(pairs_heap) | ||
| if (index1, index2) in checked: | ||
| continue | ||
| checked.add((index1, index2)) | ||
| result_pairs.append([nums1[index1], nums2[index2]]) | ||
| if index1 + 1 < len(nums1): | ||
| heappush(pairs_heap, (nums1[index1 + 1] + nums2[index2], index1 + 1, index2)) | ||
| if index2 + 1 < len(nums2): | ||
| heappush(pairs_heap, (nums1[index1] + nums2[index2 + 1], index1, index2 + 1)) | ||
| return result_pairs | ||
| ``` | ||
|
|
||
| ## Step2 | ||
|
|
||
| - 参考資料 | ||
| - https://github.com/sendahuang14/leetcode/pull/9/files | ||
| - https://github.com/TORUS0818/leetcode/pull/12 | ||
| - https://github.com/seal-azarashi/leetcode/pull/10/files | ||
| - https://github.com/ryoooooory/LeetCode/pull/17 | ||
| - [https://github.com/kazukiii/leetcode/pull/11](https://github.com/kazukiii/leetcode/pull/11#discussion_r1643725895) | ||
| - https://discord.com/channels/1084280443945353267/1235829049511903273/1246118347863621652 | ||
| - 論点 | ||
| - heapqに入れられるかどうかは、関数化してもいいかも。関数化する選択肢、自分あまり持てないがちなので注意 | ||
| - 関数化したらearly returnも使える | ||
| - set()を使わずにheapqに入れるものを制限する方法もある | ||
| - 左と上の両方が揃ってから初めて入れる | ||
| - 各行、各列で、どこまで入れたかの配列を持つ | ||
| - set()を使うのが好ましくないというシチュエーションがいまいちわからない | ||
|
|
||
| ```python | ||
| # 各行、各列でどこまで入れたかの配列をもつ | ||
| # この下でこのコードをリファクタリング | ||
| class Solution: | ||
| def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]: | ||
| index1_to_appended_index2 = [-1] * len(nums1) | ||
| index2_to_appended_index1 = [-1] * len(nums2) | ||
| sum_and_index_heap = [] | ||
| heapq.heappush(sum_and_index_heap, (nums1[0] + nums2[0], 0, 0)) | ||
|
|
||
| def add_heap_if_needed(index1, index2): | ||
| if index1 >= len(nums1) or index2 >= len(nums2): | ||
| return | ||
| if not index1_to_appended_index2[index1] == index2 - 1 or not index2_to_appended_index1[index2] == index1 - 1: | ||
| return | ||
| heapq.heappush(sum_and_index_heap, (nums1[index1] + nums2[index2], index1, index2)) | ||
|
|
||
| result_pairs = [] | ||
| while len(result_pairs) < k: | ||
| pair_sum, index1, index2 = heapq.heappop(sum_and_index_heap) | ||
| result_pairs.append([nums1[index1], nums2[index2]]) | ||
| index1_to_appended_index2[index1] = index2 | ||
| index2_to_appended_index1[index2] = index1 | ||
| add_heap_if_needed(index1 + 1, index2) | ||
| add_heap_if_needed(index1, index2 + 1) | ||
| return result_pairs | ||
| ``` | ||
|
|
||
| - 上のコードについて | ||
| - 変数名が微妙かも。index1_to_appended_index2は、index1_to_index2_in_resultとか? | ||
| - この辺(https://github.com/TORUS0818/leetcode/pull/12)見る感じ | ||
| - 関数化が意図を伝えられるというのなるほど。もうちょっと関数化して、意図を伝えたほうが良さそう?(今までのやつあんまりしてこなかったけど) | ||
| - sum_and_index_heapはデータの中身をまあ表してはいるけど、読む立場の時にcandidatesの方が意図が分かりやすそう | ||
| - len(nums1)*len(nums2)がkを超えない場合の処理も考えてみる | ||
| - 上記に気を付けて、結構関数化してみた(やり過ぎ?) | ||
|
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. 確かに処理の流れがわかりやすい気がします(バイアスがかかっているかもしれない) 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. 第一印象は「関数多過ぎじゃないか?」でしたが、読んでみるとアリだなと思いました。
Owner
Author
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. ありがとうございます。 |
||
|
|
||
| ```python | ||
|
|
||
| class Solution: | ||
| def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]: | ||
| def is_inner_index(index1, index2): | ||
| return 0 <= index1 < len(nums1) and 0 <= index2 < len(nums2) | ||
|
|
||
| def is_last_pairs(index1, index2): | ||
| return index1 == len(nums1) - 1 and index2 == len(nums2) - 1 | ||
|
|
||
| def is_before_index1_in_result(index1, index2): | ||
| return index2_to_index1_in_result[index2] == index1 - 1 | ||
|
|
||
| def is_before_index2_in_result(index1, index2): | ||
| return index1_to_index2_in_result[index1] == index2 - 1 | ||
|
|
||
| def add_candidates_if_needed(index1, index2): | ||
| if not is_inner_index(index1, index2): | ||
| return | ||
| if not is_before_index1_in_result(index1, index2) or not is_before_index2_in_result(index1, index2): | ||
| return | ||
| heappush(candidates, (nums1[index1] + nums2[index2], index1, index2)) | ||
|
|
||
| index1_to_index2_in_result = [-1] * len(nums1) | ||
| index2_to_index1_in_result = [-1] * len(nums2) | ||
| candidates = [(nums1[0] + nums2[0], 0, 0)] | ||
| result_pairs = [] | ||
| while len(result_pairs) < k: | ||
| pair_sum, index1, index2 = heappop(candidates) | ||
| result_pairs.append([nums1[index1], nums2[index2]]) | ||
| index1_to_index2_in_result[index1] = index2 | ||
| index2_to_index1_in_result[index2] = index1 | ||
| if is_last_pairs(index1, index2): | ||
| break | ||
| add_candidates_if_needed(index1 + 1, index2) | ||
| add_candidates_if_needed(index1, index2 + 1) | ||
| return result_pairs | ||
| ``` | ||
|
|
||
| [ここ](https://discord.com/channels/1084280443945353267/1235829049511903273/1246118347863621652) にあった変わったやつも実装 | ||
|
|
||
| - ジェネレータの再帰をどうするか最初わからなかったが、イテレータになるのでfor文を使えばよかった | ||
| - TLEになった。時間計算量を考えたけどわからない、、、 | ||
| - 再帰の計算量を考えるのが苦手 | ||
|
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. リンク先は yield pair(0, j) によって高速化を図っています。 generate_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. えー、例え話をしましょう。 generate_smallest_pairs(0) という関所は、[nums1[x], nums2[0]] が書かれた紙を持っている人たちが左に並んでいて、右に並んでいる人たちは generate_smallest_pairs(1) という関所を通ってきた人たちです。 さて、番人は、人を通すたびに(比較の計算をするたびに)通行税を徴収します。通行税は全部でいくらでしょう。 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.
Owner
Author
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. ありがとうございます。関所のたとえ分かりやすかったです。 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. はい。えー、k 人関所0から出てきたとしましょう。 つまり、支払われた通行料全体は、各関所での支払いの和であり、各人(出てきたかどこかの関所で引っかかっているか)の支払いの和ですね。あとは、k 人の出自がどうなっていると通行料全体が大きくなるかです。index2 が大きい人はできるだけ多くしたいんですが、しかし、j1 < j2 ならば nums1[x] + nums2[j1] <= nums1[x] + nums2[j2] という制約がありますね。 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. はい。 さて、この時に、どの列から来ているかを考えると、すべての列から1人ずつは来ているはずです。また、index2 が小さい列のほうが多いとも少なくない人数が来ています。通行料の総額が最大になるためには、後ろに寄せたほうがいいですね。 そして、k 人が通過した後、最後の n - 1 人が払った金額の合計は 1 ~ n - 1 までの和です。 だからこの差をとれば、通行料の総額の最大値が出ます。 k が十分に大きければ、(k / n) × (1 ~ n までの和)
Owner
Author
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. 少し時間かかりましたが、上の説明と下のコードでprintデバッグしてなんとか理解できました。(つもり)(nums1 = [i*1000 for i in range(10)] , nums2 = [i for i in range(10)]や、その逆で実験) ありがとうございました。 class Solution:
def kSmallestPairs(self, nums1: list[int], nums2: list[int], k: int) -> list[list[int]]:
def generate_smallest_pairs(index2):
if index2 == len(nums2):
yield [inf, inf]
return
index1 = 0
for next_pair in generate_smallest_pairs(index2 + 1):
while index1 < len(nums1) and nums1[index1] + nums2[index2] < sum(next_pair):
print(f'関所{index2}で{next_pair=}は詰まる、横入りした{nums1[index1]=}, {nums2[index2]=}が通過')
yield [nums1[index1], nums2[index2]]
index1 += 1
print(f'関所{index2}を{next_pair=}が通過', end='、')
if index1 >= len(nums1):
print('横入りは在庫切れ')
else:
print(f'{nums1[index1]=}, {nums2[index2]=}は横入り失敗')
yield next_pair
return list(islice(generate_smallest_pairs(0), k))
```
Owner
Author
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. ちなみにGoogle社内の方だと、コード見てすぐにわかる範囲(常識の範囲)なのでしょうか 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. どうでしょう。読めるかもしれませんが、こうは書かないでしょうね。
Owner
Author
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. なるほど、ありがとうございます、確かに変わった実装だなあとは思いました。(ただ選択肢に入れとくのは読む能力を上げるのでいいかなあと思いました。) |
||
|
|
||
| ```python | ||
|
|
||
| class Solution: | ||
| def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]: | ||
| def generate_smallest_pairs(index2): | ||
| if index2 == len(nums2): | ||
| yield [inf, inf] | ||
| return | ||
| index1 = 0 | ||
| for next_pair in generate_smallest_pairs(index2 + 1): | ||
| while index1 < len(nums1) and nums1[index1] + nums2[index2] < sum(next_pair): | ||
| yield [nums1[index1], nums2[index2]] | ||
| index1 += 1 | ||
| yield next_pair | ||
|
|
||
| return list(islice(generate_smallest_pairs(0), k)) | ||
| ``` | ||
|
|
||
| ## Step3 | ||
|
|
||
| ```python | ||
|
|
||
| class Solution: | ||
| def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]: | ||
| def is_valid_index(index1, index2): | ||
| return 0 <= index1 < len(nums1) and 0 <= index2 < len(nums2) | ||
|
Comment on lines
+149
to
+150
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. 一度しか呼び出されていない返り値が boolean の関数ですが、これらは関数名を見れば判定内容がある程度理解出来るようにする目的で作られていますでしょうか。もしそうなら、代わりに同じ名前の変数を宣言したり、コメントを残すようにしてもいいかなと思いました。
Owner
Author
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. なるほど、確かにぱっと見で分かりにくかったかもしれません。 |
||
|
|
||
| def is_possible_candidate(index1, index2): | ||
| if index1 == 0 or index2 == 0: | ||
| return True | ||
| return (index1 - 1, index2) in index_pairs_in_result\ | ||
|
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. 細かいですが、
Owner
Author
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. これスペースあるのは普通なんですね!ありがとうございます |
||
| and (index1, index2 - 1) in index_pairs_in_result | ||
|
|
||
| def is_last_pairs(index1, index2): | ||
| return index1 == len(nums1) - 1 and index2 == len(nums2) - 1 | ||
|
|
||
| candidates = [(nums1[0] + nums2[0], 0, 0)] | ||
| result_pairs = [] | ||
| index_pairs_in_result = set() | ||
| while len(result_pairs) < k: | ||
| pair_sum, index1, index2 = heapq.heappop(candidates) | ||
|
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.
Owner
Author
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. sumは組み込みでsum関数があるので、個人的に単体ではあんまり使いたくないと思ってこうしました 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. sum が組み込み関数なんですね (。_ 。 ) |
||
| result_pairs.append([nums1[index1], nums2[index2]]) | ||
| if is_last_pairs(index1, index2): | ||
| return result_pairs | ||
| index_pairs_in_result.add((index1, index2)) | ||
| diffs = [(0, 1), (1, 0)] | ||
| for diff in diffs: | ||
| diff1, diff2 = diff | ||
|
Comment on lines
+171
to
+172
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. for diff1, diff2 in diffs:で良いかもしれません。
Owner
Author
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. 確かに、気づいてませんでした、ありがとうございます |
||
| new_index1 = index1 + diff1 | ||
| new_index2 = index2 + diff2 | ||
| if not is_valid_index(new_index1, new_index2): | ||
| continue | ||
| if not is_possible_candidate(new_index1, new_index2): | ||
| continue | ||
|
Comment on lines
+175
to
+178
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. ひとつにまとめてもよさそうです
Owner
Author
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. これは個人的には、一つずつcontinueで可能性を潰していくイメージなので、分けたままにしたいかなあと思いました。(別問題で、 continueをこの時は自分は分けますというodaさんのコメントがあった気がしたが、どの問題か忘れてしまった) |
||
| new_sum = nums1[new_index1] + nums2[new_index2] | ||
| heapq.heappush(candidates, (new_sum, new_index1, new_index2)) | ||
| return result_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.
num_pairsは一度も使われていないため、消したほうが良いと思います。