Files
leetcode/top-100-liked/76/leetcode_test.go
T
2026-07-23 16:56:50 +08:00

157 lines
4.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package top100liked
import (
"fmt"
"testing"
)
// https://leetcode.cn/problems/find-median-from-data-stream/?envType=study-plan-v2&envId=top-100-liked
//
// 思路:双堆法
// 维护两个堆,把数据流分成"较小的一半"和"较大的一半"
// - left:大顶堆,存较小的一半,堆顶是较小半部分的最大值
// - right:小顶堆,存较大的一半,堆顶是较大半部分的最小值
// 插入时根据当前中位数(left 堆顶)决定放入哪个堆,再平衡两堆大小,
// 保证 |left.Len() - right.Len()| <= 1,这样中位数只需看两堆顶即可。
// 泛型堆,通过 compareFunc 决定是大顶堆还是小顶堆。
// compareFunc(a, b) 返回 true 表示 a 应排在 b 上面(优先级更高)。
type heap[T any] struct {
data []T
compareFunc func(T, T) bool
}
func NewHeap[T any](compareFunc func(T, T) bool) heap[T] {
return heap[T]{
compareFunc: compareFunc,
}
}
// 上浮:从 idx 向上比较,不满足堆序就与父节点交换,直到满足或到根。
func (h *heap[T]) Up(idx int) {
for (idx-1)/2 >= 0 {
cur := h.data[idx]
p := h.data[(idx-1)/2]
if h.compareFunc(cur, p) {
h.data[idx], h.data[(idx-1)/2] = h.data[(idx-1)/2], h.data[idx]
idx = (idx - 1) / 2
} else {
break
}
}
}
// 下沉:从 idx 向下比较,与优先级更高的子节点交换,直到满足堆序或成为叶子。
func (h *heap[T]) Down(idx int) {
for {
l, r := 2*idx+1, 2*idx+2
m := idx
if l < len(h.data) && h.compareFunc(h.data[l], h.data[m]) {
m = l
}
if r < len(h.data) && h.compareFunc(h.data[r], h.data[m]) {
m = r
}
if m == idx {
break
}
h.data[idx], h.data[m] = h.data[m], h.data[idx]
idx = m
}
}
// 插入:追加到末尾后上浮,保持堆序。
func (h *heap[T]) Push(val T) {
h.data = append(h.data, val)
h.Up(len(h.data) - 1)
}
// 弹出堆顶:把末尾元素换到堆顶,删除末尾,再从堆顶下沉。
func (h *heap[T]) Pop() T {
if len(h.data) <= 0 {
var zero T
return zero
}
old := h.data[0]
h.data[0] = h.data[len(h.data)-1]
h.data = h.data[:len(h.data)-1]
h.Down(0)
return old
}
func (h *heap[T]) Top() T {
if len(h.data) > 0 {
return h.data[0]
}
var zero T
return zero
}
func (h *heap[T]) Len() int {
return len(h.data)
}
// MedianFinder 用双堆维护数据流的中位数。
type MedianFinder struct {
left heap[int] // 大顶堆,存较小的一半,堆顶是该半部分最大值
right heap[int] // 小顶堆,存较大的一半,堆顶是该半部分最小值
}
// Constructorleft 用 > 构成大顶堆,right 用 < 构成小顶堆。
func Constructor() MedianFinder {
return MedianFinder{
left: NewHeap(func(i1, i2 int) bool { return i1 > i2 }),
right: NewHeap(func(i1, i2 int) bool { return i1 < i2 }),
}
}
// AddNum 插入一个数并保持两堆平衡:
// 1. 第一个数直接放 left
// 2. 后续数与 left 堆顶比较:>= 堆顶放 right(属于较大半部分),否则放 left;
// 3. 平衡:任一堆比另一堆多超过 1,就搬堆顶到另一侧。
func (this *MedianFinder) AddNum(num int) {
if this.left.Len() == 0 {
this.left.Push(num)
return
}
if num >= this.left.Top() {
this.right.Push(num)
} else {
this.left.Push(num)
}
if this.left.Len() > this.right.Len()+1 {
v := this.left.Pop()
this.right.Push(v)
}
if this.right.Len() > this.left.Len()+1 {
v := this.right.Pop()
this.left.Push(v)
}
}
// FindMedian 根据两堆大小关系求中位数:
// - 总数为偶数:取两堆顶的平均值
// - 总数为奇数:元素多的那堆堆顶就是中位数
func (this *MedianFinder) FindMedian() float64 {
switch (this.left.Len() + this.right.Len()) % 2 {
case 0:
return (float64(this.left.Top()) + float64(this.right.Top())) / 2
case 1:
if this.left.Len() > this.right.Len() {
return float64(this.left.Top())
} else {
return float64(this.right.Top())
}
}
return 0
}
func Test(t *testing.T) {
medianFinder := Constructor()
medianFinder.AddNum(1) // arr = [1]
medianFinder.AddNum(2) // arr = [1, 2]
fmt.Println(medianFinder.FindMedian()) // 返回 1.5 ((1 + 2) / 2)
medianFinder.AddNum(3) // arr[1, 2, 3]
fmt.Println(medianFinder.FindMedian()) // return 2.0
}