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
33 changes: 33 additions & 0 deletions 703-Kth-Largest-Element-in-a-Stream/note.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# 703. Kth Largest Element in a Stream

<https://leetcode.com/problems/kth-largest-element-in-a-stream/>

## step1(まず通す)

ヒープを書くのに自信がなかったので、最初にソートしてあとは2分探索で挿入してくコードを書いた。
ヒープは書こうとしてとしてめちゃくちゃ時間をかけてしまった。非常に混乱しながら書いているのでとても効率の悪いコードになっている。時間がもったいないのでもっと早く他の人のコードを読みにいくべきだった。

## step2(整形&他の人のコードを読む)

- そもそも insert している時点で挿入部分の時間計算量が配列の長さnに対して O(n) あるので2分探索じゃなくていっそ線形に探索した方が楽だった
- heapq というライブラリがあるのでそれを使えば話が早そうではある
- <https://docs.python.org/ja/3.13/library/heapq.html>
- みなさん、max ヒープを作って k 回 pop するんじゃなくて、k 要素の min ヒープを作っていらっしゃる
- <https://github.com/shining-ai/leetcode/pull/8> など
- 「k番目に大きい=大きい方からk要素だけの中で最小」なので、こうすると判定が早い
- 簡単にセルフ実装してみた。最初 push を↓のように書いていて時間計算量がO(配列長さ) だなあと思っていたところ CPython では 末尾を pop してから先頭をその値で上書きしていた。なるほどいいやり方だ。
- <https://github.com/python/cpython/blob/3.13/Lib/heapq.py#L137>
- 親の位置をビット演算で計算しているのも勉強になる
- <https://github.com/python/cpython/blob/06dce35b5a63ea653d6101d36a8afc0e922255c6/Lib/heapq.py#L212>
- 最初書いた時はクラスのメンバ変数として Minheap.heap を持たせたために self.heap を直接いじるメソッドと引数として受け取るメソッドが混在していたので改善した
- CPython の heapq は関数を集めた名前空間

```python
def pop(self) -> int:
head = self.heap[0]
# 配列をスライスしているので O(k) かかるがそれで良いのかと思ったが
self.heap = self.heapify(self.heap[1:])
return head
```

## step3(10分以内にさっとかける * 3回)
24 changes: 24 additions & 0 deletions 703-Kth-Largest-Element-in-a-Stream/step1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from typing import List


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

def add(self, val: int) -> int:
expanded_nums = [float("inf")] + self.descending_nums + [float("-inf")]
interval_head = 0
interval_tail = len(expanded_nums) - 1
interval_center = (interval_tail + interval_head) // 2
while interval_head != interval_tail:
if val >= expanded_nums[interval_center]:
interval_tail = interval_center
interval_center = (interval_tail + interval_head) // 2
else:
interval_head = interval_center + 1
interval_center = (interval_tail + interval_head) // 2

self.descending_nums.insert(interval_head - 1, val)

return self.descending_nums[self.k - 1]
41 changes: 41 additions & 0 deletions 703-Kth-Largest-Element-in-a-Stream/step1_heap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from typing import List


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

def max_heapify_root(self, nums: List[int], parent: int, tail: int):
while True:
left_child = parent * 2 + 1
right_child = (parent + 1) * 2
largest = parent
if left_child <= tail and nums[largest] < nums[left_child]:
largest = left_child
if right_child <= tail and nums[largest] < nums[right_child]:
largest = right_child

if largest != parent:
nums[parent], nums[largest] = nums[largest], nums[parent]
parent = largest
else:
return

def max_heapify(self, nums: List[int]):

tail = len(nums) - 1
for parent in range(len(nums) // 2, -1, -1):
self.max_heapify_root(nums, parent, tail)

return nums

def add(self, val: int) -> int:
self.max_heap_nums = [val] + self.max_heap_nums
self.max_heapify_root(self.max_heap_nums, 0, len(self.max_heap_nums) - 1)
rest = self.max_heap_nums
for _ in range(self.k):
rest_largest = rest[0]
rest = self.max_heapify(rest[1:])

return rest_largest
74 changes: 74 additions & 0 deletions 703-Kth-Largest-Element-in-a-Stream/step2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
from typing import List


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

def add(self, val: int) -> int:
self.push(self.k_largest_elements, val)
return self.k_largest_elements[0]

def _parent(self, child: int) -> int:
return (child - 1) // 2

def _left_child(self, parent: int) -> int:
return parent * 2 + 1

def _right_child(self, parent: int) -> int:
return (parent + 1) * 2

def _shift_up(self, heap: List[int], index: int):
while index > 0 and heap[index] < heap[self._parent(index)]:
heap[index], heap[self._parent(index)] = (
heap[self._parent(index)],
heap[index],
)
index = self._parent(index)

def _shift_down(self, heap: List[int], index: int):
heap_size = len(heap)
while True:
smallest = index
if (
self._left_child(index) < heap_size
and heap[self._left_child(index)] < heap[smallest]
):
smallest = self._left_child(index)
if (
self._right_child(index) < heap_size
and heap[self._right_child(index)] < heap[smallest]
):
smallest = self._right_child(index)

if smallest == index:
break
heap[index], heap[smallest] = (
heap[smallest],
heap[index],
)
index = smallest

def push(self, heap: List[int], num: int):
heap_size = len(heap)
if heap_size < self.k:
heap.append(num)
self._shift_up(heap, len(heap) - 1)
elif num > heap[0]:
heap[0] = num
self._shift_down(heap, 0)

def pop(self, heap: List[int]) -> int:
head = heap[0]
tail = heap.pop()
if heap:
heap[0] = tail
self._shift_down(heap, 0)
return head

def heapify(self, nums: List[int]) -> List[int]:
heap: List[int] = []
for index in range(len(nums)):
self.push(heap, nums[index])
return heap
17 changes: 17 additions & 0 deletions 703-Kth-Largest-Element-in-a-Stream/step3.py

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

ただの感想になってしまうんですが,最終形がとてもわかりやすかったです

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます!

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from typing import List
import heapq


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

def add(self, val: int) -> int:
if len(self.k_largest_elements) < self.k:
heapq.heappush(self.k_largest_elements, val)
elif val > self.k_largest_elements[0]:
heapq.heappushpop(self.k_largest_elements, val)
return self.k_largest_elements[0]