Skip to content

347. Top K Frequent Elements #10

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
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
115 changes: 115 additions & 0 deletions lc347.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# step1
愚直に考えるなら、for文を回してdictに格納し、上からk個取得
頻度(freq)でheapソートをして格納し、そのときにnumもデータに入れる。
その後heappopでk個まで減らしてreturnする。
"""

```python
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
elements = {}

for num in nums:
if num in elements:
elements[num] += 1
else:
elements[num] = 1

elements_order_by_freq = []
for key, value in elements.items():
heapq.heappush(elements_order_by_freq, [value, key])

while len(elements_order_by_freq) > k:
heapq.heappop(elements_order_by_freq)

Choose a reason for hiding this comment

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

if len() > k の時点で取り出すとメモリが少なく済みそうです。

        for key, value in elements.items():
            heapq.heappush(elements_order_by_freq, [value, key])
            if len(elements_order_by_freq) > k:
                heapq.heappop(elements_order_by_freq)


most_k_elements = []
for element_order_by_freq in elements_order_by_freq:
most_k_elements.append(element_order_by_freq[1])

return most_k_elements
```

# step2
参考にした方のPR
- https://github.com/t0hsumi/leetcode/pull/9
- https://github.com/katataku/leetcode/pull/9
- https://github.com/tarinaihitori/leetcode/pull/9
- https://github.com/thonda28/leetcode/pull/17
- https://github.com/goto-untrapped/Arai60/pull/55

他の方のPRを見ていたときのメモ
- dictを使わなくても、Counterでできる https://docs.python.org/3/library/collections.html#collections.Counter
Copy link

Choose a reason for hiding this comment

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

https://docs.python.org/3/library/collections.html#collections.Counter.most_common
most_common の実装を見てもいいかもしれません。

- [defaultdict](https://docs.python.org/ja/3.6/library/collections.html)とdictの違いがよくわかっていない。defaultdictを使うとstep1の最初のif文が必要なくなる。これは存在しないキーに対しては0が自動的に設定されているから
- 前のLeetCodeの問題でも指摘されたこと(厳密にいうと、コードを書いているときにどうやって書くんだっけとなっていた)を忘れていた.
```
for num in nums:
if num not in elemets:
elements[num] = 0
elements[num] += 1
```
- クイックセレクトを使った解法もありましたがこれくらいは"常識"なんですかね?
Copy link

Choose a reason for hiding this comment

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

Quick Sort は、授業でやるでしょう。Quick select は知っていると思います。

ただ、この問題が出題されたときに選択するかというと少し過剰に大変に思いますね。(そういう考え方も思いついたと口頭で言うくらい。)

- 他の方の解答で、returnのところに[num for num, freq in sorted\_num\_to\_freq\_list]みたいにして書いているのは個人的にはあまり好きじゃない(変数に格納してreturn 変数 みたいにしたい)が、これは個人の好みとして片付けて良いのか、"一般的な"ソフトウェアエンジニアが書く方法があるのか気になった。
Copy link

Choose a reason for hiding this comment

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

ある程度好みの範囲でしょうが、横に長くて特に順序があっちこっちいくやつは、目を移動させないといけないので読みにくいです。
https://discord.com/channels/1084280443945353267/1303257587742933024/1311262294457712652

Copy link
Owner Author

@rinost081 rinost081 Dec 18, 2024

Choose a reason for hiding this comment

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

計算や処理をしているのを変数に格納せずにreturnしているのが個人的に嫌、という理由のほかに、そのような場合は目を移動させて読むことになることが往々にしてあるからというのも感じていました。

ちなみにこちらの方を見ていて質問したいと思ったのですが、PEP8だとlambdaは非推奨とされていると思います。実際にlambdaは使われているんですかね。そもそもlambdaを使ってコードを書くことは少ない(というよりかはpythonにはこんな機能もあります!というような紹介で使っているだけの感じがある)んですが、私が使っていないだけですかね。

Copy link

Choose a reason for hiding this comment

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

Python の考え方に
https://peps.python.org/pep-0020/

There should be one-- and preferably only one --obvious way to do it.

というのがあります。

これは、Perl の

There is more than one way to do it.

とは逆です。

lambda は、様々な言語にありますが、他言語と比較したときに「式であるなど、故意にかなり使いにくくしてある」が、あるとありがたいことが稀にあるので用意されている、くらいの温度感で私は見ています。


- step1の改良
```python
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

sorted_nums = []
for num, freq in num_to_count.items():
heapq.heappush(sorted_nums, (freq, num))

top_k_freq = []
while(len(sorted_nums) > k):
Copy link

Choose a reason for hiding this comment

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

あんまり括弧は使うな、というコーディングスタイルもあるみたいです。
https://google.github.io/styleguide/pyguide.html#33-parentheses

少なくとも、whileと(の間には1つスペースがあるといい様に思いました。

heapq.heappop(sorted_nums)

for num in sorted_nums:
top_k_freq.append(num[1])

return top_k_freq
```
- バケットソート
整数のソートにおいて、最大値個のバケツを用意することでメモリ使用を犠牲にしてソートを行う。
```python
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

bucket = [[] for _ in range(len(nums) + 1)]
for num, freq in elements.items():

Choose a reason for hiding this comment

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

こちら elements -> num_to_count ですね。

Copy link
Owner Author

Choose a reason for hiding this comment

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

見落としてました,ありがとうございます

bucket[freq].append(num)

sorted_nums = []
for i in range(len(bucket) -1, 0, -1):
if bucket[i]:
sorted_nums.extend(bucket[i])
if len(sorted_nums) >= k:
return sorted_nums[:k]
Comment on lines +91 to +92

Choose a reason for hiding this comment

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

num の種類が k 未満の場合には、return に到達せずに for ループを終えるので None を返してしまいそうです。
k 未満の場合の対応をどうするかにもよりますが、ある分だけ返すようにするのであればこんな感じがよいかもです。またその場合にはスライスせず sorted_nums で十分そうです。

Suggested change
if len(sorted_nums) >= k:
return sorted_nums[:k]
if len(sorted_nums) >= k:
break
return sorted_nums

```

# step3
```python
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

sorted_nums = []
Copy link

Choose a reason for hiding this comment

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

厳密にはsortedでない違和感を感じました。

Copy link
Owner Author

Choose a reason for hiding this comment

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

確かに、何でソートしているのかがわからないですし、なんか変ですね。

Choose a reason for hiding this comment

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

同じ感想を持ちました。(heapq で使われている)min-heap では root が最小値であることが保証されているだけなので sorted とは言えなさそうです

for num, freq in num_to_count.items():
heapq.heappush(sorted_nums, (freq, num))

top_k_freq = []
Copy link

Choose a reason for hiding this comment

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

この初期化はwhile文の下に書いていいと思いました。

while(len(sorted_nums) > k):
heapq.heappop(sorted_nums)

for num in sorted_nums:
top_k_freq.append(num[1])
Comment on lines +111 to +112

Choose a reason for hiding this comment

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

num で受け取って2つ目の数を append するという処理をしていますが、for 時点で展開して受け取ることができたと思います。

Suggested change
for num in sorted_nums:
top_k_freq.append(num[1])
for _, num in sorted_nums:
top_k_freq.append(num)


return top_k_freq
```