Skip to content
Open
Show file tree
Hide file tree
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
48 changes: 48 additions & 0 deletions ara60/top-k-frequent-elements/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
## 考察
- 過去に解いたことあり
- 方針
- まずはカウントを行う
- time: O(n), space: O(n)
- カウントした結果を元にtopkを算出
- 方針は3つ程思い浮かぶ
- ソート
- nums.length <= 10^5 なので、計数ソートが良さそう

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

自分は計数ソートという単語を初めて聞きました。自分はソフトウェアエンジニアの常識には含まれていないと思うのですが、あまり自信がないです。 Computer Science Curricula 2023 には掲載されていませんでした。
nums.length <= 10^5 ですので、 O(n log n) のアルゴリズムでも十分で、そちらが想定解かもしれません。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

バケットソートですね。

思っているよりも標準ライブラリーは賢いことを色々しているので、思ったよりも標準ライブラリーのほうが速いんじゃないかなあと思います。

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.

常識の感覚大変参考になります。
たしかに標準ライブラリーは色々最適化されていることを考えると、初手では std::sort() を使うようにしようと思います。

- time: O(n), space: O(n)
- クイックセレクト
- ランダムにpivotを決める
- pivotの左側にpivot以下のもの、右側にpivotより大きいものを集める
- pivotが何番目かが確定するので、どちら側にk番目があるかは明白
- これを繰り返せばいい
- any orderなので、pivotがk番目に来たら、pivotとその左側を返せばいい
- time: O(n), space: O(1)
- ヒープを使う
- サイズkの最小ヒープを用意して、サイズを保つように入れていく
- time: O(n log k), space: O(k)
- 全部さくっと実装できるようにしておきたい
- まずはソートでやる
- あとは実装

## Step1
- 計数ソートを実装した
- time: O(n), space: O(n)

## Step2
- ヒープとクイックセレクトでも実装してみた
- 3通り実装してみて、計数ソートとクイックセレクトは脳の負荷が高い
- ヒープ使ってやるのが一番良さそう
- あとC++、エラー内容がPythonよりわかりにくく感じた(これは慣れかもしれない)

## Step3
- 1回目: 3m02s
- 2回目: 2m45s
- 3回目: 2m30s

## Step4
- レビューを元に修正
- 変数名の見直し
- num_count -> num_to_count
- min_heap -> min_count_and_nums
- key, value -> num, count
- 拡張for文のコロンの前後にはspaceを入れる
- pairの代わりにstructを使うように修正

25 changes: 25 additions & 0 deletions ara60/top-k-frequent-elements/step1.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
int n = nums.size();
unordered_map<int, int> num_count;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

何らかのマップであることを表すため、変数名に to をいれて、 num_to_count とするとよいと思います。

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.

ありがとうございます。見直してみます。

for (int num: nums) {

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 の後に空白を 1 つ空けましょう。
https://google.github.io/styleguide/cppguide.html#Horizontal_Whitespace

num_count[num]++;
}

vector<int> buckets(n + 1, 0);
for (auto [key, value]: num_count) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

key value より、変数の中に入っている値を表す num count と名付けたほうが良いと思います。

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.

ありがとうございます。こちらも見直します。

buckets[value]++;
}
for (int i = 0; i < n; ++i) {
buckets[i + 1] += buckets[i];
}

vector<int> sorted(num_count.size());
for (auto [key, value]: num_count) {
sorted[--buckets[value]] = key;
}

return vector<int>(sorted.end() - k, sorted.end());
}
};
24 changes: 24 additions & 0 deletions ara60/top-k-frequent-elements/step2_heap.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> count;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

key と value にそれぞれ何が含まれるかを表す変数名にしたほうが良いと思います。 num_to_count あたりがよいと思います。

for (int num: nums) {
count[num]++;
}

priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> min_heap;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

pair を使用すると、中に何が入っているか想起しにくくなるので、やめたほうが良いと思います。 sturct を定義し、 bool operator < () を定義し priority_queue に入れることをお勧めします。

また、 min_heap という名前は、「最小のヒープ」意味すると思うのですが、変数に含まれている値が何であるかを直接的に表現していないと思います。変数に含まれる値が何であるかを直接的に表現した英単語または英語句を付けることをお勧めします。
さらに、 heap は型が priority_queue であることから、情報が重複してしまっています。省いてよいと思います。
自分なら、 min_count_and_nums あたりがよいと思います。

@kazukiii kazukiii Jun 15, 2024

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.

ありがとうございます。pairの代わりに構造体を定義しようと思います。

さらに、 heap は型が priority_queue であることから、情報が重複してしまっています。省いてよいと思います。

たしかにheapであることは型見ればわかりますね。step4で参考にさせていただきます。

for (auto [key, value]: count) {
min_heap.push({value, key});
if (min_heap.size() > k) {
min_heap.pop();
}
}

vector<int> answer;
while (!min_heap.empty()) {
answer.push_back(min_heap.top().second);
min_heap.pop();
}
return answer;
}
};
51 changes: 51 additions & 0 deletions ara60/top-k-frequent-elements/step2_quick_select.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> count;
for (int num : nums) {
count[num]++;
}

vector<pair<int, int>> frequency_to_num;
for (auto& [key, value] : count) {
frequency_to_num.push_back({value, key});
}

int n = frequency_to_num.size();
quick_select(frequency_to_num, 0, n - 1, n - 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.

勉強と割り切って quick_select() を実装したのであればよいのですが、そうでなければ指し過ぎのように思います。 std::sort() で全体をソートして、先頭 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.

問題文に You may return the answer in any order. とあったので、quick selectも想定解の一つかと考えていました(ソートやヒープでやると順番通りになるため)。
quick selectは頭の片隅に置いておく程度に留めようと思います。

vector<int> answer;
for (int i = n - 1; i >= n - k; --i) {
answer.push_back(frequency_to_num[i].second);
}
return answer;
}

private:
void quick_select(vector<pair<int, int>>& array, int left, int right, int k) {
while (left < right) {
int pivot_index = partition(array, left, right);
if (pivot_index == k) return;
else if (pivot_index < k) left = pivot_index + 1;
else right = pivot_index - 1;
}
}

int partition(vector<pair<int, int>>& array, int left, int right) {
int pivot_index = rand_int(left, right);
int pivot_value = array[pivot_index].first;
swap(array[pivot_index], array[right]);
int store_index = left;
for (int i = left; i < right; ++i) {
if (array[i].first < pivot_value) {
swap(array[i], array[store_index]);
store_index++;
}
}
swap(array[store_index], array[right]);
return store_index;
}

int rand_int(int from, int to) {
return from + rand() % (to - from + 1);
}
};
24 changes: 24 additions & 0 deletions ara60/top-k-frequent-elements/step3.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> num_count;
for (int num: nums) {
num_count[num]++;
}

priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> min_heap;
for (auto [key, value]: num_count) {
min_heap.push({value, key});
if (min_heap.size() > k) {
min_heap.pop();
}
}

vector<int> answer;
while (!min_heap.empty()) {
answer.push_back(min_heap.top().second);
min_heap.pop();
}
return answer;
}
};
34 changes: 34 additions & 0 deletions ara60/top-k-frequent-elements/step4.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> num_to_count;
for (int num : nums) {
num_to_count[num]++;
}

priority_queue<NumCount> min_count_and_nums;
for (auto [num, count] : num_to_count) {
min_count_and_nums.push({count, num});
if (min_count_and_nums.size() > k) {
min_count_and_nums.pop();
}
}

vector<int> answer;
while (!min_count_and_nums.empty()) {
answer.push_back(min_count_and_nums.top().num);
min_count_and_nums.pop();
}
return answer;
}

private:
struct NumCount {
int count;
int num;

bool operator<(const NumCount& other) const {
return count > other.count;
}
};
};