This commit is contained in:
2026-07-20 19:57:25 +08:00
parent 4fa78d4c00
commit 124ad9afdf
26 changed files with 2715 additions and 5 deletions
+49
View File
@@ -1,6 +1,7 @@
package top100liked
import (
"container/heap"
"reflect"
"sort"
"testing"
@@ -136,6 +137,53 @@ func topKFrequentBucket(nums []int, k int) []int {
return res
}
// 解法三:container/heap 维护大小 k 的最小堆 — O(n log k) / O(n)
// 思路同解法一,但用标准库 container/heap 提供堆算法,自己只实现 heap.Interface。
//
// container/heap 两层 API(最容易踩坑的地方):
// - 你实现的 Push/Pop 只是"回调"Push 追加到切片末尾,Pop 删除末尾元素
// - 真正对外的是 heap.Push / heap.Pop:它们调你的 Push/Pop 并负责 siftUp/siftDown
// - Less 用 < 是小顶堆(堆顶最小),用 > 是大顶堆
// - Push/Pop 必须用指针接收者(要改 slice header);Len/Less/Swap 用值接收者即可
//
// 要点:
// - entryHeap []entry 实现 heap.InterfaceLen/Less/Swap + Push/Pop
// - Less 按 cnt 升序,堆顶是堆内频率最小者
// - 遍历 mheap.Push(h, entry{v,c})h.Len() > k 时 heap.Pop(h) 淘汰最小
// - 收集堆中剩余 k 个元素返回
type entryHeap []entry
func (h entryHeap) Len() int { return len(h) }
func (h entryHeap) Less(i, j int) bool { return h[i].cnt < h[j].cnt } // < 小顶堆
func (h entryHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
// Push/Pop 是给 heap 包调用的回调,只负责在切片末尾增删,不要加堆序逻辑
func (h *entryHeap) Push(x any) { *h = append(*h, x.(entry)) }
func (h *entryHeap) Pop() any {
old := *h
n := len(old)
x := old[n-1]
*h = old[:n-1]
return x
}
func topKFrequentContainerHeap(nums []int, k int) []int {
m := countFreq(nums)
h := &entryHeap{}
heap.Init(h)
for v, c := range m {
heap.Push(h, entry{v, c})
if h.Len() > k {
heap.Pop(h)
}
}
res := make([]int, 0, k)
for h.Len() > 0 {
res = append(res, heap.Pop(h).(entry).val)
}
return res
}
// equalIgnoreOrder 忽略顺序比较两个 []int。
func equalIgnoreOrder(a, b []int) bool {
if len(a) != len(b) {
@@ -167,6 +215,7 @@ func TestTopKFrequent(t *testing.T) {
}{
{"heap", topKFrequentHeap},
{"bucket", topKFrequentBucket},
{"container-heap", topKFrequentContainerHeap},
}
for _, s := range solvers {
t.Run(s.name, func(t *testing.T) {