From 33a3b618074ff43168a4cb25b84ed03b6bdbe90c Mon Sep 17 00:00:00 2001 From: nicah4o <91923071+nicah4o@users.noreply.github.com> Date: Sun, 10 May 2026 17:15:52 +0900 Subject: [PATCH 1/3] 347.top k frequent elements --- 347_top_k_frequent_element/answer.md | 133 +++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 347_top_k_frequent_element/answer.md diff --git a/347_top_k_frequent_element/answer.md b/347_top_k_frequent_element/answer.md new file mode 100644 index 0000000..240ad87 --- /dev/null +++ b/347_top_k_frequent_element/answer.md @@ -0,0 +1,133 @@ +問題:https://leetcode.com/problems/top-k-frequent-elements/description/ + +## step1 + +問題は +1.vector型の配列numsと整数kを与えられる。 +(ただし、kは最低1から最大でも「配列に一回のみ出現する値」の個数までしか取らない。例:[1,2,3,4,4]ならばkは1から3までしか取らない。) +2.配列の中で最も多く出現する値を上からk個数えてvector型の配列として返す。配列内の順番は問わない。 +というもの。 + +前回の問題で用いたpriority_queueを単純に適用することはできない。求めたいのは値の大小ではなく値の出現頻度だ。 +値が現れた回数を値とのセットで扱う必要がある。キーとそれに対応する値をpairとして格納するmapを使用する。 +(前回:https://leetcode.com/problems/kth-largest-element-in-a-stream/description/) + +まずmapに<キー, 要素> = <値, 値の出現回数>として値と出現回数を記録する。 +mapではキーから要素にアクセスできるが要素からキーを探すことはできない。今回、出現回数の大小から必要な値を求めたい。つまり要素からキーにアクセスしたい。 +だから、二つ目のmapにキーと要素を逆転して格納する。つまり、<キー, 要素> = <値の出現回数, 値>としたmapをつくる。 +しかし、mapは要素の重複は認めてもキーの重複は認めない。逆関数をmapで作るには全単射である必要があるが、同じ出現回数の異なる値がある以上不可能である。 +解決策として、二つ目のmapにはmultimapというキーの重複を許すmapを使うことでキーの重複する異なる値を格納できる。 + +最後に、vectorを作ってmultimapの後ろから走査を行う。mapは昇順、つまり最初のイテレータに最小のキー、最後のイテレータに最大のキーが並ぶ。 +出現回数の最も多い要素(値)をk個入れたvectorを返す。 + +計算量はgeminiによると空間計算量がO(M)で時間計算量がO(NlogM)になるらしい。Mはnumsのうちのユニークな要素数。 + +```cpp +class Solution { +public: + vector topKFrequent(vector& nums, int k) { + map mp; + for (int i:nums) { + if (mp.contains(i)) { + mp[i]++; + } + else { + mp[i] = 1; + } + } + multimap multi; + for (const auto& i:mp) { + multi.emplace(i.second,i.first); + } + vectorv; + std::multimap:: reverse_iterator it; + it = multi.rbegin(); + while (v.size() != k) { + v.push_back(it->second); + it++; + } + return v; + } +}; +``` +acceptされたが7msで最頻値0msより遅い。別の方法がある。 + +## step2 + +他の方のコードとコメント集みた。priority_queue使う方法とクイックセレクトという方法があるらしい。 +参考:https://discord.com/channels/1084280443945353267/1235829049511903273/1245555256360697949 + +頻度の数え上げはmapでなくunordered_mapでおこなう。unordered_mapはハッシュマップなので時間計算量がO(N)で済む。対してmapは木構造でO(NlogM)。 +あと、priority_queueでgreater(昇順)にしたもの最小ヒープとよぶらしい。最小ヒープの要素数を減らしていくと自動的にk番目に行き着く。 +priority_queueにはpairも入れることができる。firstでソートした後にsecondでソートされるようだ。 + +```cpp +class Solution { +public: + std::vector topKFrequent(std::vector& nums, int k) { + // 1. unordered_map で数え上げ (O(N)) + std::unordered_map counts; + for (int n : nums) + counts[n]++; + + // 2. 最小ヒープを作成。中身は pair<頻度, 数字> + // 頻度が低いものが top() に来るように std::greater を使う + using pi = std::pair; + std::priority_queue, std::greater> min_heap; + + for (auto const& [num, freq] : counts) { + min_heap.push({freq, num}); + if (min_heap.size() > k) { + min_heap.pop(); // 頻度が一番低いものを追い出す + } + } + + // 3. ヒープに残った K 個を取り出す + std::vector result; + while (!min_heap.empty()) { + result.push_back(min_heap.top().second); + min_heap.pop(); + } + return result; + } +}; +``` +geminiの書いたこの理想的なコードをstep3で練習。計算量は時間O(N+Mlogk)で空間O(M)。 + +```cpp +class Solution { +public: + std::vector topKFrequent(std::vector& nums, int k) { + std::unordered_map counts; + for (int n : nums) + counts[n]++; + + // (数字, 頻度) のペアを vector に移す + std::vector> unique; + for (auto const& pair : counts) { + unique.push_back(pair); + } + + // std::nth_element を使い、頻度で並び替える + // 第2引数の位置(M-k番目)に、正しい要素が来るように配置される + std::nth_element( + unique.begin(), unique.begin() + unique.size() - k, unique.end(), + [](const std::pair& a, const std::pair& b) { + return a.second < b.second; // 頻度の昇順 + }); + + // 境界線(M-k)から後ろが「上位 k 個」 + std::vector result; + for (int i = unique.size() - k; i < unique.size(); ++i) { + result.push_back(unique[i].first); + } + return result; + } +}; +``` +これもgeminiのクイックセレクト。{数字, 頻度}のペアでM-K番目の頻度を基準としてそれより小さいものを前に移転するnth_element関数。 +この関数の引数は(fist, nth, last, policy, comp)で、上のコードではcompにラムダ式を用いている。compは引数1が引数2が小さいならtrueを返すような関数を受けるほか、greater{}とかも受ける。 +参考:https://en.cppreference.com/cpp/algorithm/nth_element +計算量は時間平均O(M)で空間O(M)。最悪だと時間O(M^2)になるらしい。[1,2,3,4,5]でピボットが5だと1~4と5比較して4回。4がピボットになり3回……。 +これを繰り返すと初項(N-1)の等差数列でN(N-1)/2回の探索が必要。でN^2がでる。 From 7cdf0d9793f870dfd668af18bb24ea2f023bfd92 Mon Sep 17 00:00:00 2001 From: nicah4o <91923071+nicah4o@users.noreply.github.com> Date: Tue, 12 May 2026 00:21:59 +0900 Subject: [PATCH 2/3] Update answer.md --- 347_top_k_frequent_element/answer.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/347_top_k_frequent_element/answer.md b/347_top_k_frequent_element/answer.md index 240ad87..ed960a2 100644 --- a/347_top_k_frequent_element/answer.md +++ b/347_top_k_frequent_element/answer.md @@ -4,7 +4,8 @@ 問題は 1.vector型の配列numsと整数kを与えられる。 -(ただし、kは最低1から最大でも「配列に一回のみ出現する値」の個数までしか取らない。例:[1,2,3,4,4]ならばkは1から3までしか取らない。) +~~(ただし、kは最低1から最大でも「配列に一回のみ出現する値」の個数までしか取らない。例:[1,2,3,4,4]ならばkは1から3までしか取らない。)~~ +5/12追記:上の記述は誤りです。kは最大で配列内の数字の種類の数と同じ値を取ります。 2.配列の中で最も多く出現する値を上からk個数えてvector型の配列として返す。配列内の順番は問わない。 というもの。 From 87ab06482829860aa1bd4bf282bf8f6e86d642c1 Mon Sep 17 00:00:00 2001 From: nicah4o <91923071+nicah4o@users.noreply.github.com> Date: Tue, 12 May 2026 00:26:45 +0900 Subject: [PATCH 3/3] Update answer.md --- 347_top_k_frequent_element/answer.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/347_top_k_frequent_element/answer.md b/347_top_k_frequent_element/answer.md index ed960a2..082c0b6 100644 --- a/347_top_k_frequent_element/answer.md +++ b/347_top_k_frequent_element/answer.md @@ -4,7 +4,7 @@ 問題は 1.vector型の配列numsと整数kを与えられる。 -~~(ただし、kは最低1から最大でも「配列に一回のみ出現する値」の個数までしか取らない。例:[1,2,3,4,4]ならばkは1から3までしか取らない。)~~ +(ただし、kは最低1から最大でも「配列に一回のみ出現する値」の個数までしか取らない。例:[1,2,3,4,4]ならばkは1から3までしか取らない。) 5/12追記:上の記述は誤りです。kは最大で配列内の数字の種類の数と同じ値を取ります。 2.配列の中で最も多く出現する値を上からk個数えてvector型の配列として返す。配列内の順番は問わない。 というもの。