From fbfae4e98d046f02c53f9d483922cd6a8f6b1e37 Mon Sep 17 00:00:00 2001 From: Taito Ohsumi Date: Wed, 11 Dec 2024 00:36:17 +1100 Subject: [PATCH 1/2] Add 703. Kth Largest Element in a Stream.md --- 703. Kth Largest Element in a Stream.md | 163 ++++++++++++++++++++++++ 1 file changed, 163 insertions(+) create mode 100644 703. Kth Largest Element in a Stream.md diff --git a/703. Kth Largest Element in a Stream.md b/703. Kth Largest Element in a Stream.md new file mode 100644 index 0000000..d8c1dba --- /dev/null +++ b/703. Kth Largest Element in a Stream.md @@ -0,0 +1,163 @@ +# step 1 + +思いついた解法(n = これまで入力された要素数とする) +- sortされたリスト (要素数k分だけ昇順で記録し、list[0]でアクセス) + - init + - time complexity: O(n log(n)) + - space complexity: O(n) (Aux: O(1)) + - add + - time complexity: O(k) + - space complexity: O(k) (Aux: O(1)) +- sortされていないリスト (全ての要素を記録し、quickselect) + - init + - time complexity: O(1) + - space complexity: O(n) (Aux: O(1)) + - add + - time complexity: O(n^2) (average: O(n)) + - space complexity: O(n) (Aux: O(1)) +- heap (要素数k分だけ記録し、heap[0]でアクセス) + - init + - time complexity: O(n log(k)) (heappushをn回繰り返す場合) + - space complexity: O(k) + - add + - time complexity: O(log(k)) + - space complexity: O(k) (Aux: O(1)) + +heapを使った解法 +```python +import heapq + + +class KthLargest: + + def __init__(self, k: int, nums: List[int]): + self.heap: List[int] = [] + self.k = k + for num in nums: + if len(self.heap) < self.k: + heapq.heappush(self.heap, num) + continue + heapq.heappushpop(self.heap, num) + + def add(self, val: int) -> int: + heapq.heappush(self.heap, val) + if len(self.heap) == self.k + 1: + heapq.heappop(self.heap) + return self.heap[0] +``` + +$1 <= k <= nums.length$という問題設定に依存している回答になった。`add()`において、KthLargestが存在しない場合、これまでの最小値を返す様にしている。 + +`__init__()`にて、heapを作るのに、以下の手法を思いついた。 +- heappushをn回繰り返す(O(n log(k))) +- sortして、長さk分だけ取り出す(O(n log(n))) +- heapfyして、n-kだけheappopする(O(n + (n-k)log(n))) + + +quickselectを使った解法 +```python +import random + + +class KthLargest: + + def __init__(self, k: int, nums: List[int]): + self.array = nums + self.k = k + + def add(self, val: int) -> int: + def quickselect( + array: List[int], + left: int, + right: int, + index: int + ) -> int: + pivot_index = random.randint(left, right) + array[pivot_index], array[right] = array[right], array[pivot_index] + + pivot, less_index = array[right], left + for i in range(left, right): + if array[i] <= pivot: + array[i], array[less_index] = array[less_index], array[i] + less_index += 1 + array[less_index], array[right] = array[right], array[less_index] + if less_index == index: + return array[less_index] + elif less_index < index: + return quickselect(array, less_index + 1, right, index) + else: + return quickselect(array, left, less_index - 1, index) + + self.array.append(val) + length = len(self.array) + return quickselect(self.array, 0, length - 1, length - self.k) +``` +partitionを左右から狭めていくタイプのやり方を実装しようとして、10分ほど考えて、うまく動かなかったので、https://www.youtube.com/watch?v=XEmy13g1Qxc&ab_channel=NeetCode を見た。 +定数倍遅い解法だがquickselectの参考となった。 + +addを呼ぶ回数の多いテストケースにてTLEした。 + +# step 2 +- [quicksortのpartiton](https://www.geeksforgeeks.org/hoares-vs-lomuto-partition-scheme-quicksort/) +- pythonのlistのlength: + - https://github.com/python/cpython/blob/cef0a90d8f3a94aa534593f39b4abf98165675b9/Include/cpython/listobject.h#L30-L35 + - https://github.com/python/cpython/blob/main/Objects/listobject.c#L299-L309 + - 構造体に長さを記録しているので、よばれる度にループを回して要素数を数えているわけではない +- https://github.com/rinost081/LeetCode/pull/9/files + - 関数を呼び出す側としては、ある関数を呼び出したときに、渡した引数の中身が変更されることは、あまり想定しないように思います。関数内では原則引数の中身を変更しないことをお勧めいたします。また、特別な理由があって変更する場合は、関数コメント等に明示的にその旨を書くことをお勧めいたします。 + - python3.11のsortアルゴリズム (Timsort -> Powersort) + https://www.i-programmer.info/news/216-python/15954-python-now-uses-powersort.html +- https://github.com/BumbuShoji/Leetcode/pull/9 + - (heapの解法にて):heapの要素数がself.kよりも長くならないことはクラス全体を見ないと分からない実装になっている +- https://github.com/konnysh/arai60/pull/8/files + +思ったこと +- 変数名について、heapを用いる解法では`kth_largest`とかそういう名前が多かった。確かに、`heapq.heappush`とかやっているので`heap`だけでは不十分だと感じた。 +- `__init__()`にて`add()`を呼ぶ解法も多かった。コードの重複がないので、その解法の方が、コンパクトに収まる様に感じた。 + +partitionにてHoaresをやろうとして、off-by-one errorが取れなかった。 +引数が何で、何をするということの言語化がちゃんとできていないことが原因だと思う。日常に即した具体例とか作れると良さそう(思いつかない)。 + +```python +import heapq + + +class KthLargest: + + def __init__(self, k: int, nums: List[int]): + if k <= 0 or k > len(nums) + 1: + raise ValueError(f'Invalid input value k = {k}') + self.kth_largest_scores: List[int] = [] + self.k: int = k + for num in nums: + self.add(num) + + def add(self, val: int) -> int: + heapq.heappush(self.kth_largest_scores, val) + while len(self.kth_largest_scores) > self.k: + heapq.heappop(self.kth_largest_scores) + return self.kth_largest_scores[0] +``` + +# step 3 +```python +import heapq + + +class KthLargest: + + def __init__(self, k: int, nums: List[int]): + if k < 1 or k > len(nums) + 1: + raise ValueError(f'Invalid argument: ' + f'KthLargest class constructor value k = {k}') + self.k: int = k + self.kth_largest_scores: List[int] = [] + for num in nums: + self.add(num) + + def add(self, val: int) -> int: + heapq.heappush(self.kth_largest_scores, val) + while len(self.kth_largest_scores) > self.k: + heapq.heappop(self.kth_largest_scores) + return self.kth_largest_scores[0] +``` \ No newline at end of file From 54f65b8a3725b12c77cff4e466f7d0dbcd9a7e5b Mon Sep 17 00:00:00 2001 From: Taito Ohsumi Date: Thu, 19 Dec 2024 16:20:08 +1100 Subject: [PATCH 2/2] Add step 4 --- 703. Kth Largest Element in a Stream.md | 36 +++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/703. Kth Largest Element in a Stream.md b/703. Kth Largest Element in a Stream.md index d8c1dba..8162677 100644 --- a/703. Kth Largest Element in a Stream.md +++ b/703. Kth Largest Element in a Stream.md @@ -160,4 +160,40 @@ class KthLargest: while len(self.kth_largest_scores) > self.k: heapq.heappop(self.kth_largest_scores) return self.kth_largest_scores[0] +``` + +# step 4 +コメントまとめ +- クラスの次の行は開けないことが多い気がする +- `if k < 1 or k > len(nums) + 1:`よりは、`if not 1 <= k <= len(nums) + 1:`のほうがわかりやすい。 +- [builtin exceptions](https://docs.python.org/3/library/exceptions.html#built-in-exceptions) + +メモ +- [timsort](https://hal.science/hal-01212839) +- [powersort](https://drops.dagstuhl.de/entities/document/10.4230/LIPIcs.ESA.2018.63) +- https://github.com/python/cpython/blob/48c70b8f7dfd00a018abbac50ea987f54fa4db51/Objects/listsort.txt + + +```python +import heapq + + +class KthLargest: + + def __init__(self, k: int, nums: List[int]): + if not 1 <= k <= len(nums) + 1: + raise ValueError( + "KthLargest constructor: k is a invalid size for nums: " + f"k = {k}, nums = {nums}" + ) + self.k: int = k + self.kth_largest_heap: List[int] = [] + for num in nums: + self.add(num) + + def add(self, val: int) -> int: + heapq.heappush(self.kth_largest_heap, val) + while len(self.kth_largest_heap) > self.k: + heapq.heappop(self.kth_largest_heap) + return self.kth_largest_heap[0] ``` \ No newline at end of file