Skip to content
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
103 changes: 103 additions & 0 deletions 347. Top K Frequent Elements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# [347. Top K Frequent Elements](https://leetcode.com/problems/top-k-frequent-elements/description/)

## Step1
### 問題意図の考察
- 整数配列numsと整数kが与えられ、numsの中で最頻出するk個の要素を取り出す。
- 頻度集計、効率的な上位k要素を取り出す。
- 問題文より、O(nlogn)より良い計算量を目指す。

### 解法を考える
- バケットソート
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

私が勘違いしていたら申し訳ないですが,今回使われている手法はバケットソートとは別の方法ではないでしょうか.

* 数を数える -> num frequency (unordered_map<int, int>)
* 頻度ごとに箱へ格納
* 高頻度側から取り出してk個集める

```cpp
class Solution {
public:
std::vector<int> topKFrequent(const std::vector<int>& nums, int k) {
std::unordered_map<int, int> freq;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

自分ならキーと値にそれぞれどのようなものが格納されているか分かりやすくするため、 num_to_frequency といった名前を付けると思います。

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.

@nodchip
こちらご返信が随分と遅くなってしまい申し訳ございません。
コメントありがとうございます。

今回の問題ですが、frequenceyに関連するものが沢山出てきたため、ごちゃごちゃな命名となってしまいました。
後にもご指摘頂く、f, freqなどもいろんな邪念が入ってますね。
もう少し意図を反映させていきます。

freq.reserve(nums.size());
for (int x : nums) {
++freq[x];
}

std::vector<std::vector<int>> buckets(nums.size() + 1);
for (const auto& [val, f] : freq) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

関数の引数名が nums のため、 num としたほうが分かりやすいと思います。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

f が何を表すのかやや分かりづらく感じました。 frequency とフルスペルで書くと分かりやすくなると思います。

1 文字変数は、 i や s 等インデックスを表したり文字列を表したりすることが想像しやすいもので、かつスコープが短いものであれば、十分わかりやすいと思います。今回はこれらに当てはまらないため、やや分かりづらく感じました。

buckets[f].push_back(val);
}

std::vector<int> answer;
answer.reserve(k);
for (int f = static_cast<int>(buckets.size()) - 1;
f >= 1 && static_cast<int>(answer.size()) < k; --f) {
for (int val : buckets[f]) {
answer.push_back(val);
if (static_cast<int>(answer.size()) == k) break;
}
}
return answer;
}
};

```

## Step2

### コメント
- 解法がわからなかったので、参考のgithubより回答を確認。C++完走者をもう少し見つけたい。

- https://discord.com/channels/1084280443945353267/1367399154200088626/1371325723612151918
*実際の仕事を連想する

- std::nth_elements(線形時間) https://discord.com/channels/1084280443945353267/1183683738635346001/1185972070165782688
* C. クイックセレクトについて
* 区間 [first, last) を完全には並べ替えず、イテレータ nth が指す要素を “ソート済みならそこに来る要素” にする。
  comp(x, *nth) == true な要素は nth より前へ
  comp(*nth, x) == false な要素は nth 以降へ

- ハッシュ連想配列 ref: https://en.cppreference.com/w/cpp/container/unordered_map.html?utm_source=chatgpt.com
* テンプレート仮引数の見方が、少しずつわかってきた。ここでは、reserve()に関して確認。要素と出現回数

- vector ref: https://en.cppreference.com/w/cpp/container/vector.html?utm_source=chatgpt.com
* 動的配列の性質(連続メモリ・再確保)、bucketsという命名は少し曖昧かな。ここでは、values_by_frequency とする。
* freqもfrequencyへ変更。

- アルゴリズム戦略 実装までの余裕はなかったが、考えられる選択肢を整理。
* A. バケットソート -> 高頻度側からk個集める。
* B. 最小ヒープ(priority_queue) -> (freq, val) を最小ヒープに入れ、サイズが k を超えたら pop。
* C. クイックセレクト(選択アルゴリズム) -> (val, freq) の配列(サイズ u)を作り、第 k 大の頻度を基準に分割(平均 O(u))
* D. 周波数でソート -> vector<pair<val,freq>> を freq 降順ソートして先頭 k 個。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

周波数ではなく頻度でしょうか。



```cpp
class Solution {
public:
std::vector<int> topKFrequent(const std::vector<int>& nums, int k) {
std::unordered_map<int, int> frequency;
frequency.reserve(nums.size());
for (int x : nums) {
++frequency[x];
}

std::vector<std::vector<int>> values_by_frequency(nums.size() + 1);
for (const auto& [val, f] : frequency) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

fが一文字変数でぱっと見何を意味するか分からないのでfrequencyなどだと良いなと思ったのですが、frequencyは既にunordered_mapの名前に使われているのですね。
unordered_mapの名前をmemoで書かれているようにnum_frequencyなどにするのはいかがでしょうか?

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.

@nanae772
こちらご返信が随分と遅くなってしまい申し訳ございません。
コメントありがとうございます。

今回の問題ですが、frequenceyに関連するものが沢山出てきたため、ごちゃごちゃな命名となってしまいました。
後にもご指摘頂く、f, freqなどもいろんな邪念が入ってます。野田さんからもご指摘いただきました。。。
もう少し意図を反映させていきます。

values_by_frequency[f].push_back(val);
}

std::vector<int> answer;
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

answerは実際のところ何を表しているのかがあいまいなので別の名前などだと分かりやすいかなと思いました。
そのまま表すとtop_k_frequent_elementsになりそうですが長すぎるかもしれません。top_kとかで十分かもしれません。

answer.reserve(k);
for (int f = static_cast<int>(values_by_frequency.size()) - 1;
f >= 1 && static_cast<int>(answer.size()) < k; --f) {
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

94行目のif (static_cast<int>(answer.size()) == k) break;があるので、&& static_cast<int>(answer.size()) < kという条件は無くてもよいのかなと思いました。

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.

@nanae772
ご返信随分と遅くなり申し訳ないです。
コメントありがとうございます!

94行目 break; は 内側ループだけを抜け、外側ループはそのままだと 次の f に進みます。
しかし外側のループ条件に answer.size() < k が入っているので、k 個集めた時点で外側へ再入しない(外側の追加イテレーションを防げる)。
という設計でした。

確かにシンプルに書いた方がいいですね。もう一度書いてみます!

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 (int val : values_by_frequency[f]) {
answer.push_back(val);
if (static_cast<int>(answer.size()) == k) break;
}
}
return answer;
}
};

```

## Step3