-
Notifications
You must be signed in to change notification settings - Fork 0
Add 347. Top K Frequent Elements.md #9
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,213 @@ | ||||||||||||||||
# step 1 | ||||||||||||||||
frequencyを数える->frequencyを降順にソートする->値の大きいものから取り出してリストを作成が基本的な流れだと思った。 | ||||||||||||||||
|
||||||||||||||||
frequencyを数える作業をどうするか迷った。とりあえず思いついたのは、与えられた`nums`をソートして | ||||||||||||||||
それに対してループで走査してfrequencyを数える方法。`nums`が与えられているので、`sorted_numbers`ではなく`sorted_nums`と略しても許容範囲かと判断した。与えられた`nums`からfrequencyを数える時にも、frequencyの大きいものを順に取り出す時にもheapは使えるが、計算量が変わらない上にpythonのデフォルトはmin-heapなので`(-1 * number_of_appears, number)`にする必要があり、複雑に感じたので使わなかった。 | ||||||||||||||||
|
||||||||||||||||
`n = nums.length`として、 | ||||||||||||||||
- time complexity: O(n log(n)) | ||||||||||||||||
- space complexity: O(n) | ||||||||||||||||
```python | ||||||||||||||||
class Solution: | ||||||||||||||||
def topKFrequent(self, nums: List[int], k: int) -> List[int]: | ||||||||||||||||
sorted_nums: List[int] = sorted(nums) | ||||||||||||||||
frequency: List[Tuple(int, int)] = [] | ||||||||||||||||
index: int = 0 | ||||||||||||||||
while index < len(sorted_nums): | ||||||||||||||||
number: int = sorted_nums[index] | ||||||||||||||||
number_of_appears: int = 0 | ||||||||||||||||
while index < len(sorted_nums) and sorted_nums[index] == number: | ||||||||||||||||
index += 1 | ||||||||||||||||
number_of_appears += 1 | ||||||||||||||||
frequency.append((number_of_appears, number)) | ||||||||||||||||
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. 個人的な好みのお話で、ここの while - while について他の書き方もしてみたくなりました。 if len(sorted_nums) == 0:
return []
number: int = sorted_nums[0]
number_of_appears: int = 1
for i in range(1, len(sorted_nums)):
if sorted_nums[i] == number:
number_of_appears += 1
continue
frequency.append((number_of_appears, number))
number = sorted_nums[i]
number_of_appears = 0
frequency.append((number_of_appears, number)) 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. コメントありがとうございます。 こちらの方はネストが少ない分の読みやすさがありますね。 |
||||||||||||||||
|
||||||||||||||||
frequency.sort(reverse=True) | ||||||||||||||||
kth_most_frequent_elements: List[int] = [] | ||||||||||||||||
for i in range(k): | ||||||||||||||||
kth_most_frequent_elements.append(frequency[i][1]) | ||||||||||||||||
return kth_most_frequent_elements | ||||||||||||||||
``` | ||||||||||||||||
|
||||||||||||||||
上を解き終わったあとでdictを使う方法も思いついた。 | ||||||||||||||||
|
||||||||||||||||
`occurences`と`frequency`という似た様な意味の変数が並んでいる。 | ||||||||||||||||
`sorted_frequency`とかにしようかと思ったが、常にsortされているわけでもないので適さないと考えた。 | ||||||||||||||||
|
||||||||||||||||
`n = nums.length`、`m`を`nums`内のunique elementsの数として、 | ||||||||||||||||
- time complexity: O(n + m log(m)) | ||||||||||||||||
- space complexity: O(n) | ||||||||||||||||
```python | ||||||||||||||||
class Solution: | ||||||||||||||||
def topKFrequent(self, nums: List[int], k: int) -> List[int]: | ||||||||||||||||
occurrences: dict[Tuple(int, int)] = {} | ||||||||||||||||
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. Type Hintが違うような気がします。 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. ご指摘ありがとうございます。間違ってますね。 |
||||||||||||||||
for num in nums: | ||||||||||||||||
if num in occurrences: | ||||||||||||||||
occurrences[num] += 1 | ||||||||||||||||
else: | ||||||||||||||||
occurrences[num] = 1 | ||||||||||||||||
|
||||||||||||||||
frequency: List[Tuple(int, int)] = [] | ||||||||||||||||
for number, number_of_appears in occurrences.items(): | ||||||||||||||||
frequency.append((number_of_appears, number)) | ||||||||||||||||
|
||||||||||||||||
frequency.sort(reverse=True) | ||||||||||||||||
kth_most_frequent_elements: List[int] = [] | ||||||||||||||||
for i in range(k): | ||||||||||||||||
kth_most_frequent_elements.append(frequency[i][1]) | ||||||||||||||||
return kth_most_frequent_elements | ||||||||||||||||
``` | ||||||||||||||||
# step 2 | ||||||||||||||||
- https://github.com/katataku/leetcode/pull/9 | ||||||||||||||||
- 短期記憶限界は4-7くらい。それくらいで抑えられる様に書く | ||||||||||||||||
- https://docs.python.org/3/library/stdtypes.html#dict.setdefault | ||||||||||||||||
- 問題設定に依存した書き方をしない。今回で言うとkの値 | ||||||||||||||||
https://github.com/tarinaihitori/leetcode/pull/9/files#r1816996368 | ||||||||||||||||
> 問題設定は必ず k 種類あることになっていますが、なかった場合にどうするかは考えておいてください。 | ||||||||||||||||
特別な値を返すか、Exception を投げるか、短いものでも返すか、プログラムを止めるか、そのあたりです。 | ||||||||||||||||
- `num_to_count`の様に変数にどの様な値が含まれるか想像できる様な変数名にすべき | ||||||||||||||||
- https://github.com/tarinaihitori/leetcode/pull/9 | ||||||||||||||||
- ループないで使わない変数は変数名をつけずに`_`としてしまう。 | ||||||||||||||||
- quickselectを使っている解法もあった。k番目の要素を見つける過程で、partitionにてk番目より大きいものと小さいものに分けている。 | ||||||||||||||||
- https://github.com/thonda28/leetcode/pull/17/files | ||||||||||||||||
- ちゃんとkの値が適切でない場合の処理がされていた。 | ||||||||||||||||
`raise RuntimeError(f"The number of answers is fewer than the required {k}.")` | ||||||||||||||||
|
||||||||||||||||
- dictを用いる場合の初期値設定 | ||||||||||||||||
- [dictのmethodを用いる](https://docs.python.org/3/library/stdtypes.html#dict.setdefault) | ||||||||||||||||
- [defaultdict](https://docs.python.org/3/library/collections.html#collections.defaultdict) | ||||||||||||||||
- 書き方を工夫する | ||||||||||||||||
```python | ||||||||||||||||
if num not in occurrences: | ||||||||||||||||
occurrences[num] = 1 | ||||||||||||||||
else: | ||||||||||||||||
occurrences[num] += 1 | ||||||||||||||||
Comment on lines
+80
to
+83
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. 以下書き方のほうが個人的には好きです。
Suggested change
|
||||||||||||||||
``` | ||||||||||||||||
- dictを用いない場合では、Counterを使い、`most_common`を呼び出す。 | ||||||||||||||||
https://docs.python.org/3/library/collections.html#collections.Counter | ||||||||||||||||
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 commentThe reason will be displayed to describe this comment to others. Learn more. 共通点
相違点
思ったこと |
||||||||||||||||
|
||||||||||||||||
|
||||||||||||||||
defaultdictを使う解法 | ||||||||||||||||
```python | ||||||||||||||||
from collections import defaultdict | ||||||||||||||||
import heapq | ||||||||||||||||
|
||||||||||||||||
|
||||||||||||||||
class Solution: | ||||||||||||||||
def topKFrequent(self, nums: List[int], k: int) -> List[int]: | ||||||||||||||||
num_to_count = defaultdict(int) | ||||||||||||||||
for num in nums: | ||||||||||||||||
num_to_count[num] += 1 | ||||||||||||||||
|
||||||||||||||||
if not 0 < k <= len(num_to_count): | ||||||||||||||||
raise ValueError( | ||||||||||||||||
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. https://docs.python.org/3/library/exceptions.html#ValueError |
||||||||||||||||
f"The number of unique elements in nums is fewer than k: " | ||||||||||||||||
f"nums = {nums}, k = {k}" | ||||||||||||||||
) | ||||||||||||||||
|
||||||||||||||||
top_k_frequent = [] | ||||||||||||||||
for number, count in num_to_count.items(): | ||||||||||||||||
heapq.heappush(top_k_frequent, (count, number)) | ||||||||||||||||
if len(top_k_frequent) > k: | ||||||||||||||||
heapq.heappop(top_k_frequent) | ||||||||||||||||
return [num for _, num in top_k_frequent] | ||||||||||||||||
``` | ||||||||||||||||
|
||||||||||||||||
```python | ||||||||||||||||
from collections import Counter | ||||||||||||||||
|
||||||||||||||||
|
||||||||||||||||
class Solution: | ||||||||||||||||
def topKFrequent(self, nums: List[int], k: int) -> List[int]: | ||||||||||||||||
num_to_count = Counter() | ||||||||||||||||
for num in nums: | ||||||||||||||||
num_to_count[num] += 1 | ||||||||||||||||
|
||||||||||||||||
if not 0 < k <= len(num_to_count): | ||||||||||||||||
raise ValueError( | ||||||||||||||||
f"The number of unique elements in nums is fewer than k: " | ||||||||||||||||
f"nums = {nums}, k = {k}" | ||||||||||||||||
) | ||||||||||||||||
|
||||||||||||||||
top_k_frequent = num_to_count.most_common(k) | ||||||||||||||||
return [num for num, _ in top_k_frequent] | ||||||||||||||||
``` | ||||||||||||||||
|
||||||||||||||||
# step 3 | ||||||||||||||||
step 2の例外処理のエラーメッセージだと、k < 1の時に適切でなかった。 | ||||||||||||||||
|
||||||||||||||||
```python | ||||||||||||||||
from collections import defaultdict | ||||||||||||||||
import heapq | ||||||||||||||||
|
||||||||||||||||
|
||||||||||||||||
class Solution: | ||||||||||||||||
def topKFrequent(self, nums: List[int], k: int) -> List[int]: | ||||||||||||||||
num_to_count = defaultdict(int) | ||||||||||||||||
for num in nums: | ||||||||||||||||
num_to_count[num] += 1 | ||||||||||||||||
|
||||||||||||||||
if not 1 <= k <= len(nums): | ||||||||||||||||
raise ValueError(f"Invalid number k = {k}") | ||||||||||||||||
|
||||||||||||||||
top_k_frequent = [] | ||||||||||||||||
for number, count in num_to_count.items(): | ||||||||||||||||
heapq.heappush(top_k_frequent, (count, number)) | ||||||||||||||||
if len(top_k_frequent) > k: | ||||||||||||||||
heapq.heappop(top_k_frequent) | ||||||||||||||||
return [num for _, num in top_k_frequent] | ||||||||||||||||
``` | ||||||||||||||||
|
||||||||||||||||
# step 4 | ||||||||||||||||
コメントまとめ | ||||||||||||||||
- dictのtype annotation: https://docs.python.org/3/library/typing.html#generics | ||||||||||||||||
- defaultdictとCounterの比較など: https://github.com/t0hsumi/leetcode/pull/9#discussion_r1881199623 | ||||||||||||||||
|
||||||||||||||||
|
||||||||||||||||
counterの構築は、`Counter(nums)`で済む。 | ||||||||||||||||
```python | ||||||||||||||||
from collections import Counter | ||||||||||||||||
|
||||||||||||||||
|
||||||||||||||||
class Solution: | ||||||||||||||||
def topKFrequent(self, nums: List[int], k: int) -> List[int]: | ||||||||||||||||
num_to_count = Counter(nums) | ||||||||||||||||
|
||||||||||||||||
if not 1 <= k <= len(num_to_count): | ||||||||||||||||
raise ValueError( | ||||||||||||||||
f"topKFrequent(): k is out of range: " | ||||||||||||||||
f"k = {k}, nums = {nums}" | ||||||||||||||||
) | ||||||||||||||||
|
||||||||||||||||
top_k_frequent = num_to_count.most_common(k) | ||||||||||||||||
return [num for num, _ in top_k_frequent] | ||||||||||||||||
``` | ||||||||||||||||
|
||||||||||||||||
必要な時だけpushを行うheappushpopによる最適化を利用する場合、以下の様に書ける(https://github.com/python/cpython/blob/3.13/Lib/heapq.py#L163-L168). | ||||||||||||||||
最悪のシナリオ(`num_to_count`が昇順にiterateする場合)でのパフォーマンスはかわらない。 | ||||||||||||||||
個人的にはstep 3とさして読みやすさも変わらないので、害も益もない最適化に思った。 | ||||||||||||||||
|
||||||||||||||||
```python | ||||||||||||||||
from collections import defaultdict | ||||||||||||||||
import heapq | ||||||||||||||||
|
||||||||||||||||
|
||||||||||||||||
class Solution: | ||||||||||||||||
def topKFrequent(self, nums: List[int], k: int) -> List[int]: | ||||||||||||||||
num_to_count = defaultdict(int) | ||||||||||||||||
for num in nums: | ||||||||||||||||
num_to_count[num] += 1 | ||||||||||||||||
|
||||||||||||||||
if not 1 <= k <= len(num_to_count): | ||||||||||||||||
raise ValueError( | ||||||||||||||||
"topKFrequent(): k is out of range: " | ||||||||||||||||
f"nums = {nums}, k = {k}" | ||||||||||||||||
) | ||||||||||||||||
|
||||||||||||||||
top_k_frequent = [] | ||||||||||||||||
for num, count in num_to_count.items(): | ||||||||||||||||
if len(top_k_frequent) < k: | ||||||||||||||||
heapq.heappush(top_k_frequent, (count, num)) | ||||||||||||||||
else: | ||||||||||||||||
heapq.heappushpop(top_k_frequent, (count, num)) | ||||||||||||||||
return [num for _, num in top_k_frequent] | ||||||||||||||||
``` |
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.
min-heap を max-heap として無理やり使うのが複雑であるというのは僕も同意です。(今回は使用しないという判断なので関係ないですが)仮に使う場合はコメントを残すことは必須かなと個人的には思っています。