Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
57 changes: 57 additions & 0 deletions problems/703.kth-largest-element-in-a-stream/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
## step1
- 二分探索木みたいなのに追加していって調べるのがいいのか?
- 普通のリストだと追加が遅いと思った
- 計算量がだいぶうろ覚え
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

もし計算量を覚えようとしたということであれば、仕組みから導出できるようにもしたほうが良いかもしれません。そのほうが覚える量が少なくて済み、脳にやさしいかもしれません。

二分探索木は、子要素を辿っていくたびに、探索対象となるノードの数が半分になります。 2(探索回数) ≒ (ノード数) ですので、(探索回数) = log2(ノード数) で O(log N) になります。また、この考え方以外にも同様の考え方があると思います。ご自身にとって考えやすい考え方で理解し、忘れたときにも導出できるようにすることをおすすめします。

類題: 三角形の内角の和は何度ですか?また、その証明を導出できますか?

- とりあえず最良ではないけど毎回並び替えて解くようにする
- sortが出てくるたびsortとsortedどっちがどっちだったかと降順・昇順どっちがデフォルトだったか毎回調べてる
- sort済みなのでbisect.insort()で追加していくO(logN)だったと思う
- 調べたら場所を調べるのはO(logN)だが追加はO(N)でやっぱり遅い
- 同じ数値だった場合左から追加か右から追加かは今回気にしない
- insortはinsort_rightと一緒みたい
- importのやり方も好まれるやり方がありそう
- https://note.nkmk.me/python-list-clear-pop-remove-del/
- k番目を取り出す時にpopを使う時に末尾に近いほど計算量が少なくて済むので今回は小さい順に並んでた方が最悪計算量がマシになりそう
- 実際にはどのくらいの倍率かどうかを調べる必要がありそう
- VS Codeの拡張のtestが動かなくなった
- 抜き出さなくてよかった
- それなら毎回上位kだけ保持すればいい
- pop(0)はやっぱり遅い
- pop()にするためには値を全て負にして入れないといけなくなり手間がかかる。。。
## step2
- heapqを使うと良いみたい
- 追加時にO(logN)
- K番目に大きい/小さい値heapq.nlargest(k, iterable) / nsmallest(k, iterable): (O(NlogK)) (Kが大きい場合は効率的) 
- 一旦step2-1としてheapqの使い方を調べて解いてみる
- 多分やりたいことは合ってるはずだが、TLEした
- 計算量は最初heapq.heapifyでO(N)、addでO(NlogK)
- step1では最初のsortでO(NlogN),addでbisect.insortでO(N)でKが大きい時にstep1の方が早かった
- 今回の問題ではNと比較してKが小さいとかないので上位のみでカットする方法は最悪計算量は変わらなさそう
- 遅くなりことはおそらくないのでやれるならやった方が良い?
- 最小値の取得はheap[0] (最小値の取得): O(1)で行えるのでそこで早くなる(step2-2)
- これで劇的に早くなった26ms(メモリは25.71MB)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

この 26ms は LeetCode 上での実行時間でしょうか?
LeetCode の実行時間は誤差が大きいようですので、あまり信用しないほうが良いと思います。もし実行時間を知りたい場合は、自身でマイクロベンチマークを作り、ローカルの環境で計測数ることをお勧めいたします。

- step1も27msであまり変わらない(メモリは23.65MB)
- どっちが良いか考えたい
- 一旦他の人のコードを見る
- https://github.com/Hiroto-Iizuka/coding_practice/pull/8/files
- pushしてpopするための効率的な関数heapq.heappushpopがある
- https://github.com/mamo3gr/arai60/pull/8/files
- 全部入れてカットする方法はO(NlogN)で毎回カットする方ほうはO(NlogK)になる
- https://github.com/Yuto729/LeetCode_arai60/pull/14/files
- 計算時間が書いてあるのが良いと思った
- addにカットまで集約する
- 自前での実装はやっておくと、より親と子の位置関係などのイメージがつきそう
- https://github.com/kazizi55/coding-challenges/pull/8
- `時間計算量から、どのくらいの処理時間がかかるか推定することをおすすめします。推定の方法は過去のコードレビューコメントにあると思いますので、探すことをおすすめします。`
- 処理時間の計算までしてなかったので、やるようにする
- `self.k -> self.capacity、self.top_k_nums -> self.largest_nums`
- 問題文のkを使うので問題はないと思うが、kという表記がない場合はcapacityはすごく良いと思った

## step3
- 詰まることなくかけた
- heapの計算量と操作をイメージしながら解いた




## TODO
[] 自作heapの実装
26 changes: 26 additions & 0 deletions problems/703.kth-largest-element-in-a-stream/step1-2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#
# @lc app=leetcode id=703 lang=python3
#
# [703] Kth Largest Element in a Stream
#


# @lc code=start
from bisect import insort


class KthLargest:
def __init__(self, k: int, nums: List[int]):
self.k = k
self.sorted_nums = sorted(nums)[-k:]

def add(self, val: int) -> int:
insort(self.sorted_nums, val)
if len(self.sorted_nums) > self.k:
self.sorted_nums.pop(0)
return self.sorted_nums[0]


# Your KthLargest object will be instantiated and called as such:
# obj = KthLargest(k, nums)
# param_1 = obj.add(val)# @lc code=end
24 changes: 24 additions & 0 deletions problems/703.kth-largest-element-in-a-stream/step1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#
# @lc app=leetcode id=703 lang=python3
#
# [703] Kth Largest Element in a Stream
#


# @lc code=start
from bisect import insort


class KthLargest:
def __init__(self, k: int, nums: List[int]):
self.k = k
self.sorted_nums = sorted(nums)

def add(self, val: int) -> int:
insort(self.sorted_nums, val)
return self.sorted_nums[-self.k]


# Your KthLargest object will be instantiated and called as such:
# obj = KthLargest(k, nums)
# param_1 = obj.add(val)# @lc code=end
26 changes: 26 additions & 0 deletions problems/703.kth-largest-element-in-a-stream/step2-1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#
# @lc app=leetcode id=703 lang=python3
#
# [703] Kth Largest Element in a Stream
#

# @lc code=start
import heapq


class KthLargest:
def __init__(self, k: int, nums: List[int]):
self.k = k
self.heapifyed_values = nums
heapq.heapify(self.heapifyed_values)
Comment on lines +14 to +15
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

heapq.heapify() は引数をヒープ構造を満たすように in-place で変換するそうなので、もとの nums を変更しそうです。
https://docs.python.org/3.11/library/heapq.html#heapq.heapify

Transform list x into a heap, in-place, in linear time.


def add(self, val: int) -> int:
heapq.heappush(self.heapifyed_values, val)

return heapq.nlargest(self.k, self.heapifyed_values)[-1]


# Your KthLargest object will be instantiated and called as such:
# obj = KthLargest(k, nums)
# param_1 = obj.add(val)
# @lc code=end
32 changes: 32 additions & 0 deletions problems/703.kth-largest-element-in-a-stream/step2-2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#
# @lc app=leetcode id=703 lang=python3
#
# [703] Kth Largest Element in a Stream
#

# @lc code=start
import heapq


class KthLargest:
def __init__(self, k: int, nums: List[int]):
self.k = k
self.heapifyed_values = nums
heapq.heapify(self.heapifyed_values)
self._trim_to_top_k()

def add(self, val: int) -> int:
heapq.heappush(self.heapifyed_values, val)
self._trim_to_top_k()

return self.heapifyed_values[0]

def _trim_to_top_k(self):
while len(self.heapifyed_values) > self.k:
heapq.heappop(self.heapifyed_values)
Comment on lines +24 to +26
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

関数に切り出すのいいですね。私の感覚、これくらいが切り出すか切り出さないかの境界くらいです。



# Your KthLargest object will be instantiated and called as such:
# obj = KthLargest(k, nums)
# param_1 = obj.add(val)
# @lc code=end
32 changes: 32 additions & 0 deletions problems/703.kth-largest-element-in-a-stream/step2-3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
#
# @lc app=leetcode id=703 lang=python3
#
# [703] Kth Largest Element in a Stream
#

# @lc code=start
import heapq


class KthLargest:
def __init__(self, k: int, nums: List[int]):
self.k = k
self.top_k_values = []
for n in nums:
self.add(n)

def add(self, val: int) -> int:
heapq.heappush(self.top_k_values, val)
self._trim_to_top_k()

return self.top_k_values[0]

def _trim_to_top_k(self):
while len(self.top_k_values) > self.k:
heapq.heappop(self.top_k_values)


# Your KthLargest object will be instantiated and called as such:
# obj = KthLargest(k, nums)
# param_1 = obj.add(val)
# @lc code=end
33 changes: 33 additions & 0 deletions problems/703.kth-largest-element-in-a-stream/step3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#
# @lc app=leetcode id=703 lang=python3
#
# [703] Kth Largest Element in a Stream
#

# @lc code=start
import heapq


class KthLargest:
def __init__(self, k: int, nums: List[int]):
self.k = k
self.top_k_values = []
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

今回のケースでは問題になりませんが、必ずしもソートされているわけではないので変数名がミスリーディングさせる可能性がありそうです(私もこう書いていたのですが…)。


for n in nums:
self.add(n)

def add(self, val: int) -> int:
heapq.heappush(self.top_k_values, val)
self._trim_to_top_k()

return self.top_k_values[0]

def _trim_to_top_k(self):
while len(self.top_k_values) > self.k:
heapq.heappop(self.top_k_values)


# Your KthLargest object will be instantiated and called as such:
# obj = KthLargest(k, nums)
# param_1 = obj.add(val)
# @lc code=end