u
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
package top100liked
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// https://leetcode.cn/problems/kth-largest-element-in-an-array/?envType=study-plan-v2&envId=top-100-liked
|
||||
|
||||
func findKthLargest(nums []int, k int) int {
|
||||
n := len(nums)
|
||||
target := n - k // 第 k 大 = 升序第 n-k 位
|
||||
var quickSelect func(l, r int) int
|
||||
quickSelect = func(l, r int) int {
|
||||
if l == r {
|
||||
return nums[l]
|
||||
}
|
||||
pivot := nums[l+rand.Intn(r-l+1)]
|
||||
// 三路分区:[l..lt-1] < pivot, [lt..gt] == pivot, [gt+1..r] > pivot
|
||||
lt, gt, i := l, r, l
|
||||
for i <= gt {
|
||||
switch {
|
||||
case nums[i] < pivot:
|
||||
nums[lt], nums[i] = nums[i], nums[lt]
|
||||
lt++
|
||||
i++
|
||||
case nums[i] > pivot:
|
||||
nums[gt], nums[i] = nums[i], nums[gt]
|
||||
gt--
|
||||
default:
|
||||
i++
|
||||
}
|
||||
}
|
||||
switch {
|
||||
case target < lt:
|
||||
return quickSelect(l, lt-1)
|
||||
case target > gt:
|
||||
return quickSelect(gt+1, r)
|
||||
default:
|
||||
return pivot
|
||||
}
|
||||
}
|
||||
return quickSelect(0, n-1)
|
||||
}
|
||||
|
||||
func Test(t *testing.T) {
|
||||
findKthLargest([]int{3, 2, 1, 5, 6, 4}, 2)
|
||||
}
|
||||
@@ -0,0 +1,183 @@
|
||||
package top100liked
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// https://leetcode.cn/problems/top-k-frequent-elements/?envType=study-plan-v2&envId=top-100-liked
|
||||
// 347. 前 K 个高频元素
|
||||
//
|
||||
// 给一个整数数组 nums 和整数 k,返回出现频率前 k 高的元素,顺序任意。
|
||||
// 进阶要求时间复杂度优于 O(n log n)。
|
||||
//
|
||||
// 通用第一步:统计频率 map[int]int。下面三种解法都基于此。
|
||||
|
||||
// countFreq 统计每个元素出现次数。
|
||||
func countFreq(nums []int) map[int]int {
|
||||
m := make(map[int]int)
|
||||
for _, v := range nums {
|
||||
m[v]++
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
// 解法一:最小堆 — O(n log k) / O(n)
|
||||
// 维护大小为 k 的最小堆,堆顶是当前堆内频率最小者。
|
||||
// 逐个把 (元素, 频率) 入堆;堆大小超过 k 时弹出堆顶,淘汰频率最小的。
|
||||
// 遍历结束,堆里剩下的 k 个即前 k 高频元素。
|
||||
//
|
||||
// 实现要点(container/heap):
|
||||
// - 定义 type entry struct{ val, cnt int}
|
||||
// - 定义 type minHeap []entry,实现 heap.Interface
|
||||
// - Less 按 cnt 升序(堆顶最小)
|
||||
// - 遍历 m:heap.Push(h, e);len(*h) > k 时 heap.Pop(h)
|
||||
// - 收集堆中剩余元素返回
|
||||
type entry struct {
|
||||
val int
|
||||
cnt int
|
||||
}
|
||||
|
||||
type minHeap struct {
|
||||
data []entry
|
||||
}
|
||||
|
||||
// 上浮:新元素放末尾后,与父比较,比父小则交换向上
|
||||
// i 是需要 siftUp 的元素的 index
|
||||
func (h *minHeap) siftUp(i int) {
|
||||
for i > 0 {
|
||||
p := (i - 1) / 2
|
||||
if h.data[p].cnt > h.data[i].cnt {
|
||||
h.data[p], h.data[i] = h.data[i], h.data[p]
|
||||
i = p
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 下沉:从 i 开始,与较小子比较,比子大则交换向下。n 是有效堆大小
|
||||
func (h *minHeap) siftDown(i, n int) {
|
||||
for {
|
||||
l, r := 2*i+1, 2*i+2
|
||||
smallest := i
|
||||
if l < n && h.data[l].cnt < h.data[smallest].cnt {
|
||||
smallest = l
|
||||
}
|
||||
if r < n && h.data[r].cnt < h.data[smallest].cnt {
|
||||
smallest = r
|
||||
}
|
||||
if smallest == i {
|
||||
break
|
||||
}
|
||||
h.data[smallest], h.data[i] = h.data[i], h.data[smallest]
|
||||
i = smallest
|
||||
}
|
||||
}
|
||||
|
||||
// push:追加末尾 + 上浮
|
||||
func (h *minHeap) push(e entry) {
|
||||
idx := len(h.data)
|
||||
h.data = append(h.data, e)
|
||||
h.siftUp(idx)
|
||||
}
|
||||
|
||||
// popMin 弹出堆顶(最小 cnt):用末尾覆盖堆顶,缩容,下沉
|
||||
func (h *minHeap) popMin() entry {
|
||||
if len(h.data) == 0 {
|
||||
return entry{}
|
||||
}
|
||||
ret := h.data[0]
|
||||
h.data[0] = h.data[len(h.data)-1]
|
||||
h.data = h.data[:len(h.data)-1]
|
||||
if len(h.data) > 0 {
|
||||
h.siftDown(0, len(h.data))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (h *minHeap) len() int { return len(h.data) }
|
||||
|
||||
func topKFrequentHeap(nums []int, k int) []int {
|
||||
m := countFreq(nums)
|
||||
h := minHeap{}
|
||||
for v, c := range m {
|
||||
h.push(entry{v, c})
|
||||
if h.len() > k {
|
||||
h.popMin()
|
||||
}
|
||||
}
|
||||
res := make([]int, 0, k)
|
||||
for h.len() > 0 {
|
||||
res = append(res, h.popMin().val)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// 解法二:桶排序 — O(n) 平均 / O(n)
|
||||
// 频率取值范围 [0, n],开长度 n+1 的桶:buckets[f] 存所有频率为 f 的元素。
|
||||
// 从 buckets[n] 倒序向 buckets[1] 遍历,依次把桶内元素加入结果,凑够 k 个停止。
|
||||
//
|
||||
// 实现要点:
|
||||
// - buckets := make([][]int, len(nums)+1)
|
||||
// - for val, cnt := range m { buckets[cnt] = append(buckets[cnt], val) }
|
||||
// - for f := len(buckets)-1; f >= 0 && len(res) < k; f-- { res = append(res, buckets[f]...) }
|
||||
func topKFrequentBucket(nums []int, k int) []int {
|
||||
m := countFreq(nums)
|
||||
buckets := make([][]int, len(nums)+1)
|
||||
for v, c := range m {
|
||||
buckets[c] = append(buckets[c], v)
|
||||
}
|
||||
res := make([]int, 0, k)
|
||||
for f := len(buckets) - 1; f >= 0 && len(res) < k; f-- {
|
||||
res = append(res, buckets[f]...)
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// equalIgnoreOrder 忽略顺序比较两个 []int。
|
||||
func equalIgnoreOrder(a, b []int) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
ca := append([]int(nil), a...)
|
||||
cb := append([]int(nil), b...)
|
||||
sort.Ints(ca)
|
||||
sort.Ints(cb)
|
||||
return reflect.DeepEqual(ca, cb)
|
||||
}
|
||||
|
||||
func TestTopKFrequent(t *testing.T) {
|
||||
cases := []struct {
|
||||
name string
|
||||
nums []int
|
||||
k int
|
||||
want []int
|
||||
}{
|
||||
{"basic", []int{1, 1, 1, 2, 2, 3}, 2, []int{1, 2}},
|
||||
{"single", []int{1}, 1, []int{1}},
|
||||
{"tie", []int{1, 2}, 2, []int{1, 2}}, // 频率相同,都可返回
|
||||
{"negative", []int{4, 1, -1, 2, -1, 2, 3}, 2, []int{-1, 2}},
|
||||
{"duplicate candidates", []int{3, 0, 1, 0}, 1, []int{0}},
|
||||
}
|
||||
solvers := []struct {
|
||||
name string
|
||||
fn func([]int, int) []int
|
||||
}{
|
||||
{"heap", topKFrequentHeap},
|
||||
{"bucket", topKFrequentBucket},
|
||||
}
|
||||
for _, s := range solvers {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
for _, c := range cases {
|
||||
// 复制 nums 防止解法修改原切片
|
||||
nums := append([]int(nil), c.nums...)
|
||||
got := s.fn(nums, c.k)
|
||||
if !equalIgnoreOrder(got, c.want) {
|
||||
t.Errorf("%s/%s: got %v, want %v", s.name, c.name, got, c.want)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user