diff --git a/0973.K-Closest-Points-to-Origin/memo.md b/0973.K-Closest-Points-to-Origin/memo.md new file mode 100644 index 0000000..30570c9 --- /dev/null +++ b/0973.K-Closest-Points-to-Origin/memo.md @@ -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 + +クイックセレクトに馴染みがないのでこれを練習する。 diff --git a/0973.K-Closest-Points-to-Origin/step1_heap.py b/0973.K-Closest-Points-to-Origin/step1_heap.py new file mode 100644 index 0000000..7337a4f --- /dev/null +++ b/0973.K-Closest-Points-to-Origin/step1_heap.py @@ -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 diff --git a/0973.K-Closest-Points-to-Origin/step1_sort.py b/0973.K-Closest-Points-to-Origin/step1_sort.py new file mode 100644 index 0000000..81698d9 --- /dev/null +++ b/0973.K-Closest-Points-to-Origin/step1_sort.py @@ -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] diff --git a/0973.K-Closest-Points-to-Origin/step2_max_heap.py b/0973.K-Closest-Points-to-Origin/step2_max_heap.py new file mode 100644 index 0000000..b04ea1a --- /dev/null +++ b/0973.K-Closest-Points-to-Origin/step2_max_heap.py @@ -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] diff --git a/0973.K-Closest-Points-to-Origin/step2_quick_select.py b/0973.K-Closest-Points-to-Origin/step2_quick_select.py new file mode 100644 index 0000000..8b052f1 --- /dev/null +++ b/0973.K-Closest-Points-to-Origin/step2_quick_select.py @@ -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] diff --git a/0973.K-Closest-Points-to-Origin/step3_quick_select.py b/0973.K-Closest-Points-to-Origin/step3_quick_select.py new file mode 100644 index 0000000..7ec1d26 --- /dev/null +++ b/0973.K-Closest-Points-to-Origin/step3_quick_select.py @@ -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]