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
181 changes: 181 additions & 0 deletions 347TopKFrequentElements.md
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であることを使い、最初のループの中の発生頻度を数える処理を簡潔にした
Copy link
Copy Markdown

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を代入することはできません。

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.

ご指摘ありがとうございます。理解が曖昧だから言葉にするとボロが出てしまったという項目でした。細かいところではありますが、言語の基礎なので正確に説明できるようにしないとですね

- 問題文ではfrequencyという単語が使われいたので、cntからfreqに変更した

```Go
type numFrequency struct {
n int
Copy link
Copy Markdown

@seal-azarashi seal-azarashi Aug 1, 2024

Choose a reason for hiding this comment

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

receiver や range では一文字で表しても問題ない (参考) ですが、他のケースではどうかというのは考えてみてもいいと思います。
構造体のフィールド名が一文字にまで略されることは稀に思います。例えば次のスタイルガイドでは、variable の naming についての記述になりますが、次のように書いてあります。

Do not simply drop letters to save typing. For example Sandbox is preferred over Sbx, particularly for exported names.

from: https://google.github.io/styleguide/go/decisions#variable-names

Copy link
Copy Markdown

Choose a reason for hiding this comment

The 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

これも metricm と命名されるのにならっているだけで、自ら用途を定義しているフィールド名は name といったものになっていますね。

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.

ありがとうございます。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 {
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は一般的に関数を表すことが多いので、freqで良いと思います

heapの要素にもfreqfeldがある紛らわしさから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.

あくまで Go に限った話ですが、Google のスタイルガイドにも次のようにある通り、慣習として一文字にするのは悪くないと個人的には思います。

Abbreviations can be acceptable loop identifiers when the scope is short, for example for _, n := range nodes { ... }.

from: https://google.github.io/styleguide/go/decisions#single-letter-variable-names

ただ 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.

慣習として一文字にするのは悪くないと個人的には思います。

ありがとうございます。Goのスタイルガイドは読んだことありませんでした

たしかにbuffer writerをwにしたりするのはあるあるですね
formatterのインスタンスをfとするのもよく見るかも

ただ、freqfはちょっと違和感かなー 型名とも連動してないし って感想です (^^ ; )

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 は色んなものと被りすぎててちょっと悩ましいです笑

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.

fが関数を想起させるという点は考えていなかったです。たしかに気をつけた方が良かったですね。

if h.Len() == k && f <= h.top().freq {
continue
}
Comment on lines +100 to +102
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.

パフォーマンスクリティカルでない部分のコードだったら確かにそれで良いかもしれませんね

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
}
```
98 changes: 98 additions & 0 deletions step4.md
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
}
```