Conversation
| 各要素のそれぞれの数を計算する段階では順番は関係ないのでunordered_mapを使う | ||
| コード量は増えるがfirst、secondが何を表すのか明示した方が処理を理解しやすいと思った | ||
|
|
||
| ## 他の解法 |
There was a problem hiding this comment.
他の解法として、Bucket sort で実装してみるのもありかと思いました。
(C++ の経験がほぼないので、コードへのコメントは他の方にお任せしようと思います)
| 要素が重複しない場合は入力データの大きさに影響を受ける | ||
|
|
||
| ## ステップ2 | ||
| 自分の変数はcountを使っていたがfrequencyを使う方が伝わりやすそう |
There was a problem hiding this comment.
僕も最初はcountつかってましたが、同じくfrequencyのほうがいいと思いました。
| わかりにくい。kの数までの方が素直と感じた | ||
|
|
||
| ## Discordや他の人のコードなど | ||
| クイックセレクトも常識として知っておく |
There was a problem hiding this comment.
~kthな要素を取り出すときはquickselectがあるというのが常識そうです。
There was a problem hiding this comment.
@t-ooka
コメントありがとうございます。
quickselectが使われるときはまさにこういったときなのですね。知りませんでした。ありがとうございます!
| class Solution { | ||
| public: | ||
| vector<int> topKFrequent(vector<int>& nums, int k) { | ||
| unordered_map<int,int>nums_frequency; |
There was a problem hiding this comment.
変数名のnums_frequencyですが、整数に対して頻度なので、num_to_freqencyとかのほうが分かりやすいかと思いました。
| nums_frequency[num]++; | ||
| } | ||
|
|
||
| priority_queue<pair<int,int>>descending_numbers; |
There was a problem hiding this comment.
max heapを初期化しているとおもうのですが、降順という意味合いよりもそのままmax_heap_numbersとかでもいいのかなと思いました。
There was a problem hiding this comment.
@t-ooka
ありがとうございます。
他の指摘事項を踏まえてstep5.cppに修正版を追加しました。
| } | ||
|
|
||
| vector<pair<int, int>> descending_numbers; | ||
| for (auto num : nums_counts) { |
There was a problem hiding this comment.
for (const auto [num, count] : nums_counts) {こういう風にも書けますね
https://en.cppreference.com/w/cpp/language/range-for
range-declaration may be a structured binding declaration:
| for (auto num : nums_counts) { | ||
| descending_numbers.push_back({num.second, num.first}); | ||
| } | ||
| sort(descending_numbers.rbegin(), descending_numbers.rend()); |
There was a problem hiding this comment.
sort(descending_numbers.begin(), descending_numbers.end(), greater<pair<int, int>>());これの方が素直なのかな、という気がします
There was a problem hiding this comment.
@fhiyo レビューありがとうございます。rbegin()とrend()はあまり使わないのでしょうか?🙇
There was a problem hiding this comment.
そんなことはないと思うのですが、sortで降順に並べたかったらまず第3引数を指定するかな、という感覚が自分にはあったのでコメントしました。
たとえば逆順にiterateしたいときに for (auto it = foo.rbegin(); it != foo.rend(); it++) と書くのは自然な気がします。
ただ、そんなにC++経験ないので自信はないです 🙇
There was a problem hiding this comment.
@fhiyo
今回のように降順と明示している場合には第三引数で記述するように致します。
自分はLeetCode以外で触ったことないので助かります🙇コメントありがとうございました。
| c++には相当するものがなさそうだけどmapにを使えば実装はできる(実際にstep1で使った方法) | ||
| https://github.com/t-ooka/leetcode/pull/3/files#diff-e14e39d01f77a7c369b1f688b2e0c521b4640ce25ca0db0a4a17e789cac614f3 | ||
|
|
||
| 各要素のそれぞれの数を計算する段階では順番は関係ないのでunordered_mapを使う |
There was a problem hiding this comment.
https://discord.com/channels/1084280443945353267/1206101582861697046/1240515165582135336
unordered_map は実は遅いという話があります。
Do not default to using std::unordered_set and std::unordered_map. In the common case, query performance is unlikely to be sufficiently higher than std::map to make a difference, insert performance is slightly worse, and the memory overhead is high. This makes sense mostly for large tables where you expect a lot of lookups.
この辺の話があるので、単純に順番が関係ないからunordered_map, という考えだとむしろ遅くなるケースもありそうです。
There was a problem hiding this comment.
@fhiyo コメントありがとうございます。unordered_mapは必ずしもmapに比べて早いというわけでは無いのですね。知りませんでした🙇ありがとうございます。
| nums_frequency[num]++; | ||
| } | ||
|
|
||
| priority_queue<pair<int,int>>descending_numbers; |
There was a problem hiding this comment.
Min Heapを使ってサイズがkより大きくなったら最小をpopするを繰り返す、という方法もありそうです。
| return sorted_i; | ||
| } | ||
|
|
||
| void quickselect(vector<pair<int, int>>& counts, int left, int right, int kth_smallest) { |
There was a problem hiding this comment.
クラス内部でしか使わなさそうに見えるので、privateにして露出を防いでも良さそうです
(partition()も同様)
| unordered_map<int, int> frequency_numbers; | ||
| for (int num : nums) { | ||
| frequency_numbers[num]++; | ||
| } | ||
|
|
||
| vector<pair<int, int>> counts_numbers(frequency_numbers.begin(), frequency_numbers.end()); |
There was a problem hiding this comment.
frequency_numbersとcounts_numbersという名前は微妙な気がします。
この部分を関数化して、中でmapを作ってからvectorに変換して返すようにするのはどうでしょうか。
| if (kth_smallest == pivot_index) { | ||
| return; | ||
| } else if (kth_smallest < pivot_index) { | ||
| quickselect(counts, left, pivot_index - 1, kth_smallest); |
There was a problem hiding this comment.
あえて再帰しなくても、leftとrightを更新するだけなのでループで十分な気がしました。
|
|
||
| vector<int> top_k_numbers; | ||
| for (int i = number_count - k; i < number_count; ++i) { | ||
| int element = counts[i].first; |
There was a problem hiding this comment.
@fhiyo 誤ったものあげておりました。失礼しました。step9.cppに修正版をアップしました。
|
みなさんがしっかりコメント付けてくださったので、追加コメントは特にないのですが、強いて言うなら、それぞれの解法のプロコン比較のようなものもあると良いのかなと思いました(私もあんまりできてないのですけど、、) 今回の例で言えば、深さkで止めたMinHeapやquickselect使えば時間空間計算量はどうなるか(実際の速度はどうか)、アルゴリズムとしてわかりやすいものになるのか、それらを踏まえて今回のケースではどれが適切なのかの言及まであると、色々議論が盛り上がると思いました。 |
|
@thonda28 @t-ooka @fhiyo @TORUS0818 |
|
@TORUS0818 いったん計測のところまでまとめました。どれが適しているのか調べきれておりませんので後ほど追加します。 |
| ## 各実装の比較 | ||
| 計測値はleetcodeを使用 | ||
| **priority_queue(step5.cpp)** | ||
| 時間計算量:O(n) |
There was a problem hiding this comment.
Priority QueueのPush, Pop時には、logNの時間計算量が必要なので、
Push時は、N回 logNの操作を行うので、O(n log n)だと思います。
問題へのリンク
https://leetcode.com/problems/top-k-frequent-elements/description/
問題文(プレミアムの場合)
備考
次に解く問題の予告
373. Find K Pairs with Smallest Sums
フォルダ構成
LeetCodeの問題ごとにフォルダを作成します。
フォルダ内は、step1.cpp、step2.cpp、step3.cpp、priority_queue.cpp、quick_select.cppとmemo.mdとなります。
memo.md内に各ステップで感じたことを追記します。