-
Notifications
You must be signed in to change notification settings - Fork 0
K Closest Points To Origin #123
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,110 @@ | ||
| # 973. K Closest Points to Origin | ||
|
|
||
| ## step1 | ||
|
|
||
| まずheapを使った解法。7mほど。 | ||
|
|
||
| n = len(points)として | ||
|
|
||
| 時間計算量: O(n+klog n)、空間計算量: O(n) | ||
|
|
||
| 他の解法として、ソートを思いつく。 | ||
|
|
||
| 時間計算量: O(nlogn)、空間計算量 O(k) (in-placeならO(1)) | ||
|
|
||
| ## step2 | ||
|
|
||
| Solution の Topic、Quick select を見てもう一つの解法に気づく。 | ||
|
|
||
| 時間計算量: 平均O(n)、最悪O(n^2)、空間計算量: O(1) | ||
|
|
||
| partitionの実装方法に自信がなくwikipediaを見た | ||
|
|
||
| https://ja.wikipedia.org/wiki/%E3%82%AF%E3%82%A4%E3%83%83%E3%82%AF%E3%82%BB%E3%83%AC%E3%82%AF%E3%83%88 | ||
|
|
||
|
|
||
| https://github.com/huyfififi/coding-challenges/pull/28/changes#diff-ab6bba3ca897c3d2d653f49bad85d2a075662bc1eba6f421b9689a7e80aa58f9 | ||
|
|
||
| ヒープの使い方が異なる。こちらは時間計算量O(nlogk)、空間計算量O(k) | ||
|
|
||
| pointsを書き換えない場合に最も省メモリ。 | ||
|
|
||
| > どちらにせよ必要な処理を条件分岐から出すことで、若干私の脳への収まりがよくなったが、どちらの方が人気だろう。 | ||
|
|
||
| heappushpopは知らなかった | ||
|
|
||
| https://docs.python.org/3/library/heapq.html#heapq.heappushpop | ||
|
|
||
| 一度heappushをしてから条件分岐をするかどうかについて。個人的にはメソッドとなっているheappushpopを使う方効率的なので良いと思う。heapの順序入れ替えの操作が一度で済む。 | ||
|
|
||
| heapq.nsmallest()を使う解法 | ||
|
|
||
| ```python | ||
| import heapq | ||
|
|
||
|
|
||
| class Solution: | ||
| def kClosest(self, points: list[list[int]], k: int) -> list[list[int]]: | ||
| return heapq.nsmallest(k, points, key=lambda p: pow(p[0], 2) + pow(p[1], 2)) | ||
| ``` | ||
|
|
||
| > pow() 関数を呼び出すより、 step2 のように、 x * x + y * y としたほうが読みやすく、処理が軽そうなイメージがあります。ただ、処理の重さ軽さについては、 Python のインタープリター自体が重いため、あまり気にしなくても良いかもしれません。 | ||
|
|
||
|
|
||
| disライブラリというものがあるらしい。disassemblerのライブラリ。自分でも触ってみる。 | ||
|
|
||
| https://docs.python.org/ja/3/library/dis.html | ||
|
|
||
| ```python | ||
| def pow(a, b): | ||
| "Same as a ** b." | ||
| return a ** b | ||
| ``` | ||
|
|
||
| ```text | ||
| # x * x | ||
| 4 0 RESUME 0 | ||
|
|
||
| 5 2 LOAD_FAST 0 (x) | ||
| 4 LOAD_FAST 0 (x) | ||
| 6 BINARY_OP 5 (*) | ||
| 10 RETURN_VALUE | ||
|
|
||
| # x**2 | ||
| 8 0 RESUME 0 | ||
|
|
||
| 9 2 LOAD_FAST 0 (x) | ||
| 4 LOAD_CONST 1 (2) | ||
| 6 BINARY_OP 8 (**) | ||
| 10 RETURN_VALUE | ||
|
|
||
| # pow(x, 2) | ||
| 12 0 RESUME 0 | ||
|
|
||
| 13 2 LOAD_GLOBAL 1 (NULL + pow) | ||
| 12 LOAD_FAST 0 (x) | ||
| 14 LOAD_CONST 1 (2) | ||
| 16 CALL 2 | ||
| 24 RETURN_VALUE | ||
| ``` | ||
|
|
||
| これを見ても x * xが良さそう | ||
|
|
||
| ## その他 | ||
|
|
||
| https://github.com/ryosuketc/leetcode_grind75/pull/28 | ||
|
|
||
|
|
||
| https://github.com/naoto-iwase/leetcode/pull/74/changes | ||
|
|
||
| 「YAGNI」という知らない用語があった。 | ||
|
|
||
| > 「YAGNI(ヤグニ)」とは、ソフトウェア開発における非常に有名な原則(設計思想)の一つで、「You Aren't Gonna Need It(どうせそれ、必要にならないよ)」の頭文字を取ったもの | ||
|
|
||
| https://ja.wikipedia.org/wiki/YAGNI | ||
|
|
||
|
|
||
|
|
||
| ## step3 | ||
|
|
||
| クイックセレクトに馴染みがないのでこれを練習する。 | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import heapq | ||
|
|
||
|
|
||
| class Solution: | ||
| def kClosest(self, points: list[list[int]], k: int) -> list[list[int]]: | ||
| def square_distance_to_origin(point): | ||
| return point[0] ** 2 + point[1] ** 2 | ||
|
|
||
| heap = [(square_distance_to_origin(point), i) for i, point in enumerate(points)] | ||
| heapq.heapify(heap) | ||
|
|
||
| k_closest = [] | ||
| while k > 0: | ||
| _, i = heapq.heappop(heap) | ||
| k_closest.append(points[i]) | ||
| k -= 1 | ||
|
|
||
| return k_closest |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| class Solution: | ||
| def kClosest(self, points: list[list[int]], k: int) -> list[list[int]]: | ||
| def square_distance_to_origin(point): | ||
| return point[0] ** 2 + point[1] ** 2 | ||
|
|
||
| ordered_points = sorted(points, key=square_distance_to_origin) | ||
| return ordered_points[:k] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import heapq | ||
|
|
||
|
|
||
| class Solution: | ||
| def kClosest(self, points: list[list[int]], k: int) -> list[list[int]]: | ||
| def square_distance_to_origin(point): | ||
| return point[0] ** 2 + point[1] ** 2 | ||
|
|
||
| max_heap = [] | ||
| for point in points: | ||
| if len(max_heap) < k: | ||
| heapq.heappush_max( | ||
| max_heap, | ||
| (square_distance_to_origin(point), point), | ||
| ) | ||
| else: | ||
| heapq.heappushpop_max( | ||
| max_heap, (square_distance_to_origin(point), point) | ||
| ) | ||
|
|
||
| return [point for _, point in max_heap] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| import random | ||
|
|
||
|
|
||
| class Solution: | ||
| def kClosest(self, points: list[list[int]], k: int) -> list[list[int]]: | ||
| def square_distance(point): | ||
| return point[0] ** 2 + point[1] ** 2 | ||
|
|
||
| def get_pivot_index(start, last): | ||
| return start + random.randint(0, last - start) | ||
|
|
||
| def partition(start, last, pivot_index): | ||
| pivot_value = square_distance(points[pivot_index]) | ||
|
|
||
| points[pivot_index], points[last] = points[last], points[pivot_index] | ||
| len_fixed = start | ||
| for i in range(start, last): | ||
| if square_distance(points[i]) < pivot_value: | ||
| points[i], points[len_fixed] = points[len_fixed], points[i] | ||
| len_fixed += 1 | ||
|
|
||
| points[last], points[len_fixed] = points[len_fixed], points[last] | ||
| return len_fixed | ||
|
|
||
| def k_closest_helper(start, last): | ||
| if start >= last: | ||
| return | ||
|
|
||
| pivot_index_before = get_pivot_index(start, last) | ||
| pivot_index_after = partition(start, last, pivot_index_before) | ||
|
|
||
| if pivot_index_after == k: | ||
| return | ||
| elif pivot_index_after < k: | ||
| return k_closest_helper(pivot_index_after + 1, last) | ||
| else: | ||
| return k_closest_helper(start, pivot_index_after - 1) | ||
|
|
||
| k_closest_helper(0, len(points) - 1) | ||
| return points[:k] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import random | ||
|
|
||
|
|
||
| class Solution: | ||
| def kClosest(self, points: list[list[int]], k: int) -> list[list[int]]: | ||
| def square_distance(point): | ||
| return point[0] * point[0] + point[1] * point[1] | ||
|
|
||
| def get_pivot_index(start, last): | ||
| return start + random.randint(0, last - start) | ||
|
|
||
| def partition(start, last, pivot_index): | ||
| pivot_value = square_distance(points[pivot_index]) | ||
| points[pivot_index], points[last] = points[last], points[pivot_index] | ||
|
|
||
| len_fixed = 0 | ||
| for i in range(start, last): | ||
| if square_distance(points[i]) < pivot_value: | ||
| points[i], points[start + len_fixed] = ( | ||
| points[start + len_fixed], | ||
| points[i], | ||
| ) | ||
| len_fixed += 1 | ||
|
|
||
| points[last], points[start + len_fixed] = ( | ||
| points[start + len_fixed], | ||
| points[last], | ||
| ) | ||
| return start + len_fixed | ||
|
|
||
| def k_closest_helper(start, last): | ||
| if start >= last: | ||
| return | ||
|
|
||
| pivot_index_before = get_pivot_index(start, last) | ||
| pivot_index_after = partition(start, last, pivot_index_before) | ||
|
|
||
| if pivot_index_after == k: | ||
| return | ||
| elif pivot_index_after < k: | ||
| return k_closest_helper(pivot_index_after + 1, last) | ||
| else: | ||
| return k_closest_helper(start, pivot_index_after - 1) | ||
|
|
||
| k_closest_helper(0, len(points) - 1) | ||
| return points[: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. 念のため、ぐらいのコメントです。
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 comment
The reason will be displayed to describe this comment to others. Learn more.
heapの長さをkで絞りつつpushしていけば、空間計算量はO(k)で済ませることができそうです。その場合、時間計算量はO(n log k)ですかね。(下の方に書いてありました)
Arai60の範囲でヒープ等を使って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.
Arai60の問題は以下だと思います。
https://leetcode.com/problems/kth-largest-element-in-a-stream/description/
heapの長さをkで保つ解法の発想も持っておこうと思います。今回は他の方のコードを見るまで思い浮かびませんでした。