-
Notifications
You must be signed in to change notification settings - Fork 0
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
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,181 @@ | ||
| ### Step 1 | ||
| - 最初に思いついたのはヒープを使う方法 | ||
| - numとその発生回数をマップに溜めた後、順にminヒープにpushしていく。ヒープのノード数は常にkで抑え、残ったものが最大頻度をもつk個の要素。 | ||
| - ヒープのノード数を常にk以下に抑えるので、時間計算量はO(n logk) | ||
| - 見積もり実行時間:10^5 * log 10^5 ≒ 10^6。これを1億で割って、10^-2 s = 10 ms。 | ||
| - よって、最悪のケースで10msかかるだろう。 | ||
| - 空間計算量はO(n) | ||
| - テストケースで細かいミスを修正した後に一発でACしたのは嬉しかった | ||
|
|
||
| ```Go | ||
| type numCount struct { | ||
| n int | ||
| cnt int | ||
| } | ||
|
|
||
| type numCountMinHeap []numCount | ||
|
|
||
| func (h numCountMinHeap) Len() int { return len(h) } | ||
| func (h numCountMinHeap) Less(i, j int) bool { return h[i].cnt < h[j].cnt } | ||
| func (h numCountMinHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } | ||
|
|
||
| func (h *numCountMinHeap) Push(x any) { *h = append(*h, x.(numCount)) } | ||
|
|
||
| func (h *numCountMinHeap) Pop() any { | ||
| n := len(*h) | ||
| min := (*h)[n-1] | ||
| *h = (*h)[:n-1] | ||
| return min | ||
| } | ||
|
|
||
| func (h numCountMinHeap) top() numCount { return h[0] } | ||
|
|
||
| func topKFrequent(nums []int, k int) []int { | ||
| numCntHashMap := make(map[int]int) // {num: cnt} | ||
| for _, n := range nums { | ||
| if _, exist := numCntHashMap[n]; exist { | ||
| numCntHashMap[n]++ | ||
| continue | ||
| } | ||
| numCntHashMap[n] = 1 | ||
| } | ||
|
|
||
| h := &numCountMinHeap{} | ||
| for n, c := range numCntHashMap { | ||
| if h.Len() == k && c <= h.top().cnt { | ||
| continue | ||
| } | ||
| heap.Push(h, numCount{n: n, cnt: c}) | ||
| if h.Len() > k { | ||
| heap.Pop(h) | ||
| } | ||
| } | ||
|
|
||
| topKFrequentElems := []int{} | ||
| for h.Len() > 0 { | ||
| top := heap.Pop(h).(numCount) | ||
| topKFrequentElems = append(topKFrequentElems, top.n) | ||
| } | ||
|
|
||
| return topKFrequentElems | ||
| } | ||
| ``` | ||
|
|
||
| ### Step 2 | ||
| - step1をブラッシュアップ | ||
| - Goのint型のデフォルト値(nil)が0であることを使い、最初のループの中の発生頻度を数える処理を簡潔にした | ||
| - 問題文ではfrequencyという単語が使われいたので、cntからfreqに変更した | ||
|
|
||
| ```Go | ||
| type numFrequency struct { | ||
| n int | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. receiver や range では一文字で表しても問題ない (参考) ですが、他のケースではどうかというのは考えてみてもいいと思います。
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 上記のページ全体を眺めてみましたが、フィールド名が一文字になっていたのはこちらの例ぐらいでした。 type Counter struct {
m *Metric
}
// from: https://google.github.io/styleguide/go/decisions#receiver-typeこれも metric が
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ありがとうございます。numFrequency.numとなると冗長かなと思い、numFrequencyという構造体名からnの意味を推測できるのでこうしておりましたが、goコミュニティの中で一文字フィールド名は稀ということでしたら慣習に従った方が良さそうですね |
||
| freq int | ||
| } | ||
|
|
||
| type numFreqMinHeap []numFrequency | ||
|
|
||
| func (h numFreqMinHeap) Len() int { return len(h) } | ||
| func (h numFreqMinHeap) Less(i, j int) bool { return h[i].freq < h[j].freq } | ||
| func (h numFreqMinHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } | ||
|
|
||
| func (h *numFreqMinHeap) Push(x any) { *h = append(*h, x.(numFrequency)) } | ||
|
|
||
| func (h *numFreqMinHeap) Pop() any { | ||
| n := len(*h) | ||
| min := (*h)[n-1] | ||
| *h = (*h)[:n-1] | ||
| return min | ||
| } | ||
|
|
||
| func (h numFreqMinHeap) top() numFrequency { return h[0] } | ||
|
|
||
| func topKFrequent(nums []int, k int) []int { | ||
| numFreqHashMap := make(map[int]int) // {num: freq} | ||
| for _, n := range nums { | ||
| numFreqHashMap[n]++ | ||
| } | ||
|
|
||
| h := &numFreqMinHeap{} | ||
| for n, f := range numFreqHashMap { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
heapの要素にも There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. あくまで Go に限った話ですが、Google のスタイルガイドにも次のようにある通り、慣習として一文字にするのは悪くないと個人的には思います。
ただ f を関数かと推測してしまいそう、というのは確かにそうですね。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
ありがとうございます。Goのスタイルガイドは読んだことありませんでした たしかにbuffer writerを ただ、 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. たしかに、f は色んなものと被りすぎててちょっと悩ましいです笑
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fが関数を想起させるという点は考えていなかったです。たしかに気をつけた方が良かったですね。 |
||
| if h.Len() == k && f <= h.top().freq { | ||
| continue | ||
| } | ||
|
Comment on lines
+100
to
+102
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 読みやすさを優先してこの条件分岐をなくしてしまうのも手だと思いました
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. パフォーマンスクリティカルでない部分のコードだったら確かにそれで良いかもしれませんね |
||
| heap.Push(h, numFrequency{n: n, freq: f}) | ||
| if h.Len() > k { | ||
| heap.Pop(h) | ||
| } | ||
| } | ||
|
|
||
| topKFreqElems := []int{} | ||
| for h.Len() > 0 { | ||
| top := heap.Pop(h).(numFrequency) | ||
| topKFreqElems = append(topKFreqElems, top.n) | ||
| } | ||
|
|
||
| return topKFreqElems | ||
| } | ||
| ``` | ||
|
|
||
| - Discordで他の人の解答を見ていたら、バケットソートを使っている人がいたので真似をした | ||
| - 時間計算量O(n)で実現できており、感動 | ||
| - 見積もり実行時間:1ms | ||
| - 参照:https://github.com/hayashi-ay/leetcode/pull/60/files | ||
|
|
||
| ```Go | ||
| func topKFrequent(nums []int, k int) []int { | ||
| numToFreq := make(map[int]int) | ||
| maxFreq := 0 | ||
| for _, n := range nums { | ||
| numToFreq[n]++ | ||
| if numToFreq[n] > maxFreq { | ||
| maxFreq = numToFreq[n] | ||
| } | ||
| } | ||
|
|
||
| numFreqBuckets := make([][]int, maxFreq + 1) | ||
| for n, f := range numToFreq { | ||
| numFreqBuckets[f] = append(numFreqBuckets[f], n) | ||
| } | ||
|
|
||
| topKFreqElems := []int{} | ||
| for i := maxFreq; i >= 0; i-- { | ||
| if len(topKFreqElems) >= k { | ||
| break | ||
| } | ||
| topKFreqElems = append(topKFreqElems, numFreqBuckets[i]...) | ||
| } | ||
| return topKFreqElems | ||
| } | ||
| ``` | ||
|
|
||
| ### Step 3 | ||
| - 最終的なコード。Step2のバケットソートでやった | ||
| - step2からの変更箇所 | ||
| - maxFreq変数の更新にビルトインのmax関数を使った。2023年にリリースされたGo1.21で追加されたものらしいが、知らなかった。使う言語の最新verを追うことも大事 | ||
| - 最後のループのbreakする部分をtopKFreqElemsの更新の後にした。その方が自然 | ||
|
|
||
| ```Go | ||
| func topKFrequent(nums []int, k int) []int { | ||
| numToFreq := make(map[int]int) | ||
| maxFreq := 0 | ||
| for _, n := range nums { | ||
| numToFreq[n]++ | ||
| maxFreq = max(maxFreq, numToFreq[n]) | ||
| } | ||
|
|
||
| numFreqBuckets := make([][]int, maxFreq + 1) | ||
| for n, f := range numToFreq { | ||
| numFreqBuckets[f] = append(numFreqBuckets[f], n) | ||
| } | ||
|
|
||
| topKFreqElems := []int{} | ||
| for i := maxFreq; i >= 0; i-- { | ||
| topKFreqElems = append(topKFreqElems, numFreqBuckets[i]...) | ||
| if len(topKFreqElems) >= k { | ||
| break | ||
| } | ||
| } | ||
|
|
||
| return topKFreqElems | ||
| } | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,98 @@ | ||
| ### Step 4 | ||
| - ヒープ | ||
| - 修正点 | ||
| - `numFrequency`構造体のフィールドは、広い行数に渡って使用されるので省略しすぎない | ||
|
|
||
| ```Go | ||
| type numFrequency struct { | ||
| num int | ||
| frequency int | ||
| } | ||
|
|
||
| type numFrequencyHeap []numFrequency | ||
|
|
||
| func (h numFrequencyHeap) Len() int { return len(h) } | ||
| func (h numFrequencyHeap) Less(i, j int) bool { return h[i].frequency < h[j].frequency } | ||
| func (h numFrequencyHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } | ||
|
|
||
| func (h *numFrequencyHeap) Push(x any) { | ||
| *h = append(*h, x.(numFrequency)) | ||
| } | ||
|
|
||
| func (h *numFrequencyHeap) Pop() any { | ||
| l := len(*h) | ||
| min := (*h)[l-1] | ||
| *h = (*h)[:l-1] | ||
| return min | ||
| } | ||
|
|
||
| func (h numFrequencyHeap) top() (numFrequency, error) { | ||
| if h.Len() == 0 { | ||
| return numFrequency{num: 0, frequency: 0}, errors.New("Empty heap") | ||
| } | ||
| return h[0], nil | ||
| } | ||
|
|
||
| func topKFrequent(nums []int, k int) []int { | ||
| numToFrequency := make(map[int]int) | ||
| for _, n := range nums { | ||
| numToFrequency[n]++ | ||
| } | ||
|
|
||
| h := &numFrequencyHeap{} | ||
| heap.Init(h) | ||
|
|
||
| for n, freq := range numToFrequency { | ||
| top, _ := h.top() | ||
| if h.Len() == k && freq <= top.frequency { | ||
| continue | ||
| } | ||
|
|
||
| heap.Push(h, numFrequency{num: n, frequency: freq}) | ||
| if h.Len() > k { | ||
| heap.Pop(h) | ||
| } | ||
| } | ||
|
|
||
| ans := make([]int, 0, k) | ||
| for h.Len() > 0 { | ||
| top := heap.Pop(h).(numFrequency) | ||
| fmt.Println(top) | ||
| ans = append(ans, top.num) | ||
| } | ||
|
|
||
| return ans | ||
| } | ||
| ``` | ||
|
|
||
| - バケットソート | ||
|
|
||
| ```Go | ||
| func topKFrequent(nums []int, k int) []int { | ||
| numToFrequency := make(map[int]int) | ||
| maxFrequency := 0 | ||
| for _, n := range nums { | ||
| numToFrequency[n]++ | ||
| maxFrequency = max(maxFrequency, numToFrequency[n]) | ||
| } | ||
|
|
||
| numFreqBuckets := make([][]int, maxFrequency+1) | ||
| for n, freq := range numToFrequency { | ||
| numFreqBuckets[freq] = append(numFreqBuckets[freq], n) | ||
| } | ||
|
|
||
| ans := make([]int, 0, k) | ||
| for i := len(numFreqBuckets) - 1; i >= 0; i-- { | ||
| if len(numFreqBuckets[i]) == 0 { | ||
| continue | ||
| } | ||
|
|
||
| ans = append(ans, numFreqBuckets[i]...) | ||
| if len(ans) == k { | ||
| break | ||
| } | ||
| } | ||
|
|
||
| return ans | ||
| } | ||
| ``` |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nilはゼロ値とよばれる事もありますが、Goのnilと数値の0は概念的には異なります。
Numeric以外の型のいくつかのデフォルト値がnilというのは正しいですが、例えばint型の変数にnilを代入することはできません。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ご指摘ありがとうございます。理解が曖昧だから言葉にするとボロが出てしまったという項目でした。細かいところではありますが、言語の基礎なので正確に説明できるようにしないとですね