373. Find K Pairs with Smallest Sums - #10
Conversation
| while len(pairs) < k: | ||
| _, index1, index2 = heapq.heappop(candidates) | ||
| pairs.append([nums1[index1], nums2[index2]]) | ||
| if index2 == 0 and index1 + 1 < len(nums1): |
There was a problem hiding this comment.
一行あたりの文字数が多いときは適宜改行すると読みやすくなると思います。
if index2 == 0 and index1 + 1 < len(nums1):
heapq.heappush(candidates,(
nums1[index1 + 1] + nums2[index2],
index1 + 1,
index2
))
if index2 + 1 < len(nums2):
heapq.heappush(candidates,(
nums1[index1] + nums2[index2 + 1],
index1,
index2 + 1
))参考として、PEP 8のMaximum Line Lengthの記述を引用いたします。
Limit all lines to a maximum of 79 characters.
There was a problem hiding this comment.
ありがとうございます。pep 8 のそちらのルールは存じ上げなかったので勉強になりました。
step 4として追記しました。
b354320
| - (x - 1, y) in added and (x, y - 1) in added は redundancy guard。add_to_candidates_if_necessary(i + 1, j) と add_to_candidates_if_necessary(i, j + 1) で同じ (x, y) の組み合わせになった時にダブりが起きないようにしている。 | ||
| - 処理ごとで関数に分けると、明確に認知負荷が下がる感じがする。局所的に意味がわかるようになるからだろう。 | ||
| - ちなみに visited をただの list にすると TLE になる。そんなに効率性に差があるのかと改めて実感。 | ||
| - Time Complexity: O(k log k), Space Complexity: O(k) |
There was a problem hiding this comment.
計算量正しそうです.なぜこの計算量になるのかという考えの道筋が書かれていると嬉しいです.
あとは,計算量だけではなく,実際にどれくらいの時間がかかるのか,という点にも触れられているとなお良いと思います
odaさんの記事のこのあたりです.長めに引用してみます.
https://nuc.hatenadiary.org/entry/2025/11/29/#%E7%A7%92%E3%81%A7%E3%81%AE%E5%88%A4%E6%96%AD
プログラムを書くというのは、なにか目的があって、その目的を満たすために行うものである。(中略)正確に時間を見積もるのは難しいので、事前に終わるか分からない場合もあるだろう。その場合はちょうど難しいところなので計測してみるという話になるかもしれない。いずれにしても具体的に何秒なのかが見積もれないといけない。計算量はその計算をするための手段なので、計算量が分かるのに計算時間が見積もれないと驚く。計算量はあくまでも極限を取ったときの漸近的な振る舞いのことなので、それが直接何かの根拠になることはない。たとえば、社内研修を開く仕事を任されて計画を立てている。全員の弁当を手配することになった。その時に、弁当代は来場予定社員数に比例することしか情報が出てこないと動揺する。単価が500円でも3000円でも概算でいいので予算を提示して欲しい。これと同じことだ。熱伝導方程式の形から、火の通りやすさは厚さが半分になると4倍になるが、ある厚さの肉がどれくらいで焼けるかの目安がないと実践できない。これと同じことだ。
There was a problem hiding this comment.
自分はたとえば以下のような感じに書いています.
// 入力の長さは 0 <= N <= 5 * 10^3
// 最悪ケースですべてのノードを辿ったとする,かつGoの秒間ステップ数を10^8とすると
// 5 * 10^-5 なので,0.05 ms程度
ちなみに,自分も何度か同じ指摘を受けてやるようになりました.
同じ問題のPRを見直したところ,恥ずかしながらそのときは全然計算量について言及がありませんでした.笑
There was a problem hiding this comment.
ありがとうございます!
計算量正しそうです.なぜこの計算量になるのかという考えの道筋が書かれていると嬉しいです.
あとは,計算量だけではなく,実際にどれくらいの時間がかかるのか,という点にも触れられているとなお良いと思います
確かにその通りですね。
oda さんの記事にあるように、計算量はそれが直接何かの根拠になることはなく、それが具体的に何秒かかるかの方が実際は求められるなと腑に落ちました。
この解法の時間計算量の場合、最悪ケースの k = 10^4 で考えた場合、ヒープの最大サイズも約 k になります。 while ループで k 回処理を回し、各ループ内の heappop/heappush が log (k) かかるため、全体の計算量は O(k log k) になります。
具体的な実行時間として、こちら を元に、Pythonの秒間ステップ数を 10^7 と仮定すると: 10^4 × log(10^4) ≈ 10,000 × 13.3 = 133,000 ステップ、 1.33 × 10^5 / 10^7 = 0.0133 秒となり、約 13.3 ms 程度で完了するため、十分高速に処理できると考えられるという感じですね。
また、miyataka さんもやっているように、空間計算量も同様にどれくらいメモリが使われるか見積もった方がコスト感のイメージがより湧きやすそうですね。(社内研修のお弁当を手配するにしても、数が極端に多くなるなら運ぶ車の数とかもきっと見積もりが必要だろうなと思うので)
今回の場合だと、保持する主なデータは、結果を格納する pairs とヒープ candidates の2つのみで、これらも最大要素数はそれぞれ約 k 個になります。
アルゴリズムとしての純粋なデータサイズ(64bit環境の整数やポインタ=8Byte基準)で計算すると
- pairs: 2つの整数の配列 × 10^4 = 2 × 8B × 10^4 = 160 KB
- candidates: 3つの整数(sum, idx1, idx2)のタプル × 10^4 = 3 × 8B × 10^4 = 240 KB データ本体だけであれば 合計 400 KB 程度になります。
実際には、Python特有のオブジェクトオーバーヘッド(int型でも内部的に28Byte消費するなど)が発生しますが、これを加味しても全体で 2〜3 MB 程度に収まるはずです。
今後も実際の所要時間と消費メモリを計算していきたいと思います。改めてありがとうございました。
Co-authored-by: Takayuki Miyahara <voyager.3taka28@gmail.com>
| ```py | ||
| class Solution: | ||
| def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]: | ||
| candidates = [ (nums1[0] + nums2[0], 0, 0) ] |
There was a problem hiding this comment.
[] の内側にはスペースを空けないことが多いように思いました。
candidates = [(nums1[0] + nums2[0], 0, 0)]There was a problem hiding this comment.
ありがとうございます。
確かに他の方のコードを見ていてもスペースを空けていないことの方が多いように思えますし、PEP 8 にもそのような記載がありましたね。以降心がけます。
https://peps.python.org/pep-0008/#pet-peeves
https://leetcode.com/problems/find-k-pairs-with-smallest-sums/description/
Next: https://leetcode.com/problems/two-sum/