-
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?
Conversation
問題文: https://leetcode.com/problems/find-k-pairs-with-smallest-sums/description/ You are given two integer arrays nums1 and nums2 sorted in non-decreasing order and an integer k. Define a pair (u, v) which consists of one element from the first array and one element from the second array. Return the k pairs (u1, v1), (u2, v2), ..., (uk, vk) with the smallest sums.
|
|
||
| - ジェネレータの再帰をどうするか最初わからなかったが、イテレータになるのでfor文を使えばよかった | ||
| - TLEになった。時間計算量を考えたけどわからない、、、 | ||
| - 再帰の計算量を考えるのが苦手 |
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.
リンク先は yield pair(0, j) によって高速化を図っています。
generate_smallest_pairs の再帰の木構造を想像してください。一列に並んでいますね。
index2 が大きい数字は、出力までに index2 回 yield されますね。
だから、ここに自乗が付きます。
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.
えー、例え話をしましょう。
generate_smallest_pairs という名前の関所があり、並ぶところが2つあります。
左の列から来た人と右の列から来た人が、それぞれ関所の番人に紙を渡します。番人は和が小さい方を通します。
generate_smallest_pairs(0) という関所は、[nums1[x], nums2[0]] が書かれた紙を持っている人たちが左に並んでいて、右に並んでいる人たちは generate_smallest_pairs(1) という関所を通ってきた人たちです。
generate_smallest_pairs(1) という関所は、[nums1[x], nums2[1]] が書かれた紙を持っている人たちが左に並んでいて、右に並んでいる人たちは generate_smallest_pairs(2) という関所を通ってきた人たちです。
さて、番人は、人を通すたびに(比較の計算をするたびに)通行税を徴収します。通行税は全部でいくらでしょう。
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.
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.
ありがとうございます。関所のたとえ分かりやすかったです。
とすると、まだちょっと混乱しているのですが、index2が大きいものが比較されるときにたくさん関所を通るので時間がかかるということですよね。ただgenerate_smallest_pairs(0)に辿り着くまでに途中で勝負に負けるかもしれなくて、そこを計算するとどうなるのかがいまいちわかってないです
そのコードも面白そうなのでできれば読んでみます👀
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.
はい。えー、k 人関所0から出てきたとしましょう。
その人達が元々いた場所を確認すると、index2 + 1 回関所でお金を払ってますね。まあ、負けたことがあるかもしれませんが、そのときは勝った人が通行料を払っています。たしかに、何回か勝ったけれどもまだ関所0から出てきていない人というのはいますね。ただ Generator なので、引っかかっている人の数は関所の数で抑えられそうです。
つまり、支払われた通行料全体は、各関所での支払いの和であり、各人(出てきたかどこかの関所で引っかかっているか)の支払いの和ですね。あとは、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 comment
The reason will be displayed to describe this comment to others. Learn more.
はい。
len(nums2) を n としましょう。
k 人通過した後、関所に一回でも券を見せたことがある人は通行料を払って外に出ましょう。
(関所は、Generator ですから関所の外から人を一人寄越せといわれるまでは止まっていて、必要となったら2人を呼び比較します。だから、各関所の外に一人ずつ待機しています。ただし、最後の関所は1人しか呼ばないので待機は0人です。)
合計、n - 1 + k 人が外に出ますね。
さて、この時に、どの列から来ているかを考えると、すべての列から1人ずつは来ているはずです。また、index2 が小さい列のほうが多いとも少なくない人数が来ています。通行料の総額が最大になるためには、後ろに寄せたほうがいいですね。
そして、k 人が通過した後、最後の n - 1 人が払った金額の合計は 1 ~ n - 1 までの和です。
だからこの差をとれば、通行料の総額の最大値が出ます。
k が十分に大きければ、(k / n) × (1 ~ n までの和)
n が十分に大きければ、n - k + (1 ~ k までの和)
でしょうか。
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.
少し時間かかりましたが、上の説明と下のコードでprintデバッグしてなんとか理解できました。(つもり)(nums1 = [i*1000 for i in range(10)] , nums2 = [i for i in range(10)]や、その逆で実験)
nが大きい場合のn - kの項が入る理由が最初混乱していましたが、最初は「自分の後ろの関所までの中で最も小さい値」がわからないので、最後の関所まで確認する必要がありますね。
横入りがたくさん起きると、次回「自分の後ろの関所までの中で最も小さい値」を確認しなくてすむので、通行料があんまり取れないですね。
ありがとうございました。
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))
```
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.
ちなみにGoogle社内の方だと、コード見てすぐにわかる範囲(常識の範囲)なのでしょうか
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.
どうでしょう。読めるかもしれませんが、こうは書かないでしょうね。
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.
なるほど、ありがとうございます、確かに変わった実装だなあとは思いました。(ただ選択肢に入れとくのは読む能力を上げるのでいいかなあと思いました。)
|
|
||
| class Solution: | ||
| def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]: | ||
| num_pairs = 0 |
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 は一度も使われていないため、消したほうが良いと思います。
| for diff in diffs: | ||
| diff1, diff2 = diff |
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.
for diff1, diff2 in diffs:で良いかもしれません。
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.
確かに、気づいてませんでした、ありがとうございます
| - 関数化が意図を伝えられるというのなるほど。もうちょっと関数化して、意図を伝えたほうが良さそう?(今までのやつあんまりしてこなかったけど) | ||
| - sum_and_index_heapはデータの中身をまあ表してはいるけど、読む立場の時にcandidatesの方が意図が分かりやすそう | ||
| - len(nums1)*len(nums2)がkを超えない場合の処理も考えてみる | ||
| - 上記に気を付けて、結構関数化してみた(やり過ぎ?) |
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.
確かに処理の流れがわかりやすい気がします(バイアスがかかっているかもしれない)
他の人の意見も聞いてみたいですね。
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.
第一印象は「関数多過ぎじゃないか?」でしたが、読んでみるとアリだなと思いました。
ちなみに、クラスのメソッドとしてではなく、関数の中の関数として定義したのは、その方が視覚的にわかりやすいからですか?私自身、ヘルパー関数を関数の中と外どちらで定義するかいつも少し悩んでしまうので、それ以外の理由もあったら知りたいです。
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.
ありがとうございます。
自分なりの主観的基準で一般的かどうかわからないのですが、他のメソットにも使えそうなある程度汎用的だと思ったら外、そうでないなら中にしてます。
| 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 comment
The 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 comment
The reason will be displayed to describe this comment to others. Learn more.
これスペースあるのは普通なんですね!ありがとうございます
| 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 comment
The reason will be displayed to describe this comment to others. Learn more.
sumでも十分かもと思いました
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.
sumは組み込みでsum関数があるので、個人的に単体ではあんまり使いたくないと思ってこうしました
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.
sum が組み込み関数なんですね (。_ 。 )
知りませんでした
| def is_valid_index(index1, index2): | ||
| return 0 <= index1 < len(nums1) and 0 <= index2 < len(nums2) |
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.
一度しか呼び出されていない返り値が boolean の関数ですが、これらは関数名を見れば判定内容がある程度理解出来るようにする目的で作られていますでしょうか。もしそうなら、代わりに同じ名前の変数を宣言したり、コメントを残すようにしてもいいかなと思いました。
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.
なるほど、確かにぱっと見で分かりにくかったかもしれません。
コメントを残しておきます。ありがとうございます
| if not is_valid_index(new_index1, new_index2): | ||
| continue | ||
| if not is_possible_candidate(new_index1, new_index2): | ||
| continue |
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.
ひとつにまとめてもよさそうです
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.
これは個人的には、一つずつcontinueで可能性を潰していくイメージなので、分けたままにしたいかなあと思いました。(別問題で、 continueをこの時は自分は分けますというodaさんのコメントがあった気がしたが、どの問題か忘れてしまった)
問題文: https://leetcode.com/problems/find-k-pairs-with-smallest-sums/description/
You are given two integer arrays nums1 and nums2 sorted in non-decreasing order and an integer k.
Define a pair (u, v) which consists of one element from the first array and one element from the second array.
Return the k pairs (u1, v1), (u2, v2), ..., (uk, vk) with the smallest sums.