Skip to content
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

Top K Frequent Elements #20

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
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
28 changes: 28 additions & 0 deletions pullrequests/top_k_frequent_elements/step1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
//lint:file-ignore U1000 Ignore all unused code
package topkfrequentelements

/*
rihib marked this conversation as resolved.
Show resolved Hide resolved
時間:13分
とりあえずO(n)で解けそうだったので解いてみました。
(あとで他の人のPRを読んでこれがバケットソートと呼ばれているものだと知りました、、)
*/
func topKFrequentStep1(nums []int, k int) []int {
freq := make(map[int]int)
for _, n := range nums {
freq[n]++
}

cs := make([][]int, len(nums))
for n, c := range freq {
cs[c-1] = append(cs[c-1], n)
}

var res []int
for i := len(cs) - 1; i >= 0; i-- {
res = append(res, cs[i]...)
if len(res) >= k {
break
}
}
return res
}
21 changes: 21 additions & 0 deletions pullrequests/top_k_frequent_elements/step2.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//lint:file-ignore U1000 Ignore all unused code
package topkfrequentelements

/*
Step1の変数名などをより明確になるように変更しました。
*/
func topKFrequentStep2(nums []int, k int) []int {
frequency := make(map[int]int)
for _, num := range nums {
frequency[num]++
}
countToNum := make([][]int, len(nums)+1)
for num, count := range frequency {
countToNum[count] = append(countToNum[count], num)
}
topK := make([]int, 0, k)
for i := len(countToNum) - 1; i >= 0 && len(topK) < k; i-- {
topK = append(topK, countToNum[i]...)
}
return topK
}
190 changes: 190 additions & 0 deletions pullrequests/top_k_frequent_elements/step3.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
//lint:file-ignore U1000 Ignore all unused code
package topkfrequentelements

import (
"container/heap"
"math/rand/v2"
"sort"
)

/*
他の人のPRを見て、他の解法も実装してみました。
クイックセレクトはピボットの選択としてランダムなものと中央値を用いるものの両方を実装しました(中央値の中央値はやらなくても良いかなと思い飛ばしました)。
*/
type Element struct {
num int
count int
}
Comment on lines +14 to +17

Choose a reason for hiding this comment

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

実際に開発をしていると、独自定義の構造体には適切な Go Doc Comment が欲しいなと思うことが多いです。面接時にコードに反映させるかどうかは状況次第だと思いますが、次のように書いておくとより親切になるだろうな、ぐらいのことが考えられているとより良いように思います: https://tip.golang.org/doc/comment#type

ちなみにちゃんと書いておくとこのような見た目で出力することが出来、便利です: https://blog.lufia.org/entry/2018/05/14/150400


func topKFrequentBucketSort(nums []int, k int) []int {
frequency := make(map[int]int)
for _, num := range nums {
frequency[num]++
}
countToNum := make([][]int, len(nums)+1)
for num, count := range frequency {
countToNum[count] = append(countToNum[count], num)
}
topK := make([]int, 0, k)
for i := len(countToNum) - 1; i >= 0 && len(topK) < k; i-- {
topK = append(topK, countToNum[i]...)

Choose a reason for hiding this comment

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

特に問題はないですが、この解法だけ一般にはkを超える配列を返却する可能性があるな、と思いました。
(Leetcodeの制約では It is guaranteed that the answer is unique. なので、解として間違っているという事ではありません)

※Goのmakeの3つ目の引数のキャパシティは、要素数が増えたら勝手にスケールするんですね。

Copy link
Owner Author

Choose a reason for hiding this comment

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

GoでHeapを使う場合はInterfaceの実装が必要なんですね。

はい、おっしゃる通りです。なので、特に使わないメソッドについてもインターフェースを満たすように実装しています。

※Goのmakeの3つ目の引数のキャパシティは、要素数が増えたら勝手にスケールするんですね。

その分、新しい領域の確保と要素のコピーのオーバーヘッドが発生するので、kを超える配列が発生する場合にそれを避けたい場合は、ユニークな要素数(len(countToNum))分のキャパシティを確保する必要があるのかと思いますが、topKの要素数よりもユニークな要素数がはるかに大きいことが予想できる場合はメモリが無駄になるので考えものですね、、

}
return topK
}

func topKFrequentQuickselect(nums []int, k int) []int {
frequency := make(map[int]int)
for _, num := range nums {
frequency[num]++
}
elements := make([]Element, 0, len(frequency))
for num, count := range frequency {
elements = append(elements, Element{num: num, count: count})
}
quickselect(elements, 0, len(elements)-1, len(elements)-k, partitionRandom)
topK := make([]int, k)
for i := 0; i < k; i++ {
topK[i] = elements[len(elements)-1-i].num
}
return topK
}

func quickselect(
elements []Element, left, right, k int,
partition func([]Element, int, int) int) {
for left < right {
pivotIndex := partition(elements, left, right)
if pivotIndex == k {
return
}
if pivotIndex < k {
left = pivotIndex + 1
} else {
right = pivotIndex - 1
}
}
}

func partitionRandom(elements []Element, left, right int) int {
pivotIndex := left + rand.IntN(right-left+1)
elements[pivotIndex], elements[right] = elements[right], elements[pivotIndex]
pivot := elements[right].count
storeIndex := left
for i := left; i < right; i++ {
if elements[i].count < pivot {
elements[i], elements[storeIndex] = elements[storeIndex], elements[i]
storeIndex++
}
}
elements[storeIndex], elements[right] = elements[right], elements[storeIndex]
return storeIndex
}

func partitionMedianOf3(elements []Element, left, right int) int {
mid := left + (right-left)/2
if elements[right].count < elements[left].count {
elements[right], elements[left] = elements[left], elements[right]
}
if elements[mid].count < elements[left].count {
elements[mid], elements[left] = elements[left], elements[mid]
}
if elements[right].count < elements[mid].count {
elements[right], elements[mid] = elements[mid], elements[right]
}
pivotIndex := mid
elements[pivotIndex], elements[right] = elements[right], elements[pivotIndex]
pivot := elements[right].count
storeIndex := left
for i := left; i < right; i++ {
if elements[i].count < pivot {
elements[i], elements[storeIndex] = elements[storeIndex], elements[i]
storeIndex++
}
}
elements[storeIndex], elements[right] = elements[right], elements[storeIndex]
return storeIndex
}

func topKFrequentPDQSort(nums []int, k int) []int {
frequency := make(map[int]int)
for _, num := range nums {
frequency[num]++
}
numsSet := make([]int, 0, len(frequency))
for num := range frequency {
numsSet = append(numsSet, num)
}
sort.Slice(numsSet, func(i, j int) bool {
return frequency[numsSet[i]] > frequency[numsSet[j]]
})
return numsSet[:k]
}

func topKFrequentMinHeap(nums []int, k int) []int {
frequency := make(map[int]int)
for _, num := range nums {
frequency[num]++
}
h := &MinHeap{}
heap.Init(h)
for num, count := range frequency {
heap.Push(h, Element{num: num, count: count})
if h.Len() > k {
heap.Pop(h)
}
}
topK := make([]int, 0, k)
for h.Len() > 0 {
topK = append(topK, heap.Pop(h).(Element).num)
}
return topK
}

type MinHeap []Element

func (h *MinHeap) Push(x interface{}) {
*h = append(*h, x.(Element))
}

func (h *MinHeap) Pop() interface{} {
x := (*h)[len(*h)-1]
*h = (*h)[0 : len(*h)-1]
return x
}

func (h MinHeap) Len() int { return len(h) }
func (h MinHeap) Less(i, j int) bool { return h[i].count < h[j].count }
func (h MinHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }

func topKFrequentMaxHeap(nums []int, k int) []int {
frequency := make(map[int]int)
for _, num := range nums {
frequency[num]++
}
h := &MaxHeap{}
heap.Init(h)
for num, count := range frequency {
heap.Push(h, Element{num: num, count: count})
}
topK := make([]int, 0, k)
for i := 0; i < k; i++ {
topK = append(topK, heap.Pop(h).(Element).num)
}
return topK
}

type MaxHeap []Element

func (h *MaxHeap) Push(x interface{}) {
*h = append(*h, x.(Element))
}

func (h *MaxHeap) Pop() interface{} {
x := (*h)[len(*h)-1]
*h = (*h)[0 : len(*h)-1]
return x
}

func (h MaxHeap) Len() int { return len(h) }
func (h MaxHeap) Less(i, j int) bool { return h[i].count > h[j].count }
func (h MaxHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }