Lesson 4
Finding Prefix Medians Using Heaps in Go
Introduction

Greetings, aspiring Go developer! Today, we're diving into high-level data manipulation with a crucial data structure in Go — heaps. Heaps are powerful for implementing efficient algorithms, particularly in finding quick median solutions. In Go, we leverage the container/heap package to manage heap operations effectively. Ready to explore how heaps can simplify complex algorithmic challenges? Let's dive in!

Task Statement

Your task is to create a Go function named PrefixMedian that takes an array of unique integers as input. The integers will range from 1 to (10^6), and the array length will be between 1 and 1000. The function should return a slice consisting of the medians of all prefixes of the input array.

A prefix of an array is a contiguous subsequence starting from the first element. The median of a sequence of numbers is the middle value when sorted. If the sequence has an even length, the median is the element in the position length / 2 - 1.

For example, given the input array [1, 9, 2, 8, 3], the output should be [1, 1, 2, 2, 3].

Heap and Its Operations

In Go, heaps are managed using the container/heap package, enabling us to perform heap operations efficiently. A Min Heap maintains its smallest element at the root, while a Max Heap maintains its largest. Using Min and Max Heaps together is an effective way to dynamically calculate medians as elements are processed.

Min Heap and Max Heap Implementation

In Go, we leverage the container/heap package to handle heap operations. Here's how to implement Min Heap and Max Heap using interfaces and heap operations:

Go
1package main 2 3import ( 4 "container/heap" 5 "fmt" 6) 7 8type MinHeap []int 9 10func (h MinHeap) Len() int { return len(h) } 11func (h MinHeap) Less(i, j int) bool { return h[i] < h[j] } 12func (h MinHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } 13 14func (h *MinHeap) Push(x interface{}) { 15 *h = append(*h, x.(int)) 16} 17 18func (h *MinHeap) Pop() interface{} { 19 old := *h 20 n := len(old) 21 x := old[n-1] 22 *h = old[0 : n-1] 23 return x 24} 25 26type MaxHeap []int 27 28func (h MaxHeap) Len() int { return len(h) } 29func (h MaxHeap) Less(i, j int) bool { return h[i] > h[j] } 30func (h MaxHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] } 31 32func (h *MaxHeap) Push(x interface{}) { 33 *h = append(*h, x.(int)) 34} 35 36func (h *MaxHeap) Pop() interface{} { 37 old := *h 38 n := len(old) 39 x := old[n-1] 40 *h = old[0 : n-1] 41 return x 42}
Initializing Heaps and Median Slice

Now that we have the heap structures in place, let's initialize them along with a slice to store the medians.

Go
1func PrefixMedian(arr []int) []int { 2 maxHeap := &MaxHeap{} 3 minHeap := &MinHeap{} 4 heap.Init(maxHeap) 5 heap.Init(minHeap) 6 medians := make([]int, len(arr)) 7}
Distributing Elements into Heaps

As we iterate through the array, we decide which heap to add each element to, using Go idioms. Finally, store the medians during each step. In cases where heap sizes are equal, take the median from maxHeap, otherwise from minHeap:

Go
1func PrefixMedian(arr []int) []int { 2 maxHeap := &MaxHeap{} 3 minHeap := &MinHeap{} 4 heap.Init(maxHeap) 5 heap.Init(minHeap) 6 medians := make([]int, len(arr)) 7 8 for i, num := range arr { 9 if maxHeap.Len() > 0 && num < (*maxHeap)[0] { 10 heap.Push(maxHeap, num) 11 } else { 12 heap.Push(minHeap, num) 13 } 14 15 // Balance the heaps 16 if maxHeap.Len() > minHeap.Len()+1 { 17 heap.Push(minHeap, heap.Pop(maxHeap)) 18 } else if minHeap.Len() > maxHeap.Len() { 19 heap.Push(maxHeap, heap.Pop(minHeap)) 20 } 21 22 // Extract median 23 if maxHeap.Len() == minHeap.Len() { 24 medians[i] = (*maxHeap)[0] 25 } else { 26 medians[i] = (*minHeap)[0] 27 } 28 } 29 30 return medians 31} 32 33func main() { 34 arr := []int{1, 9, 2, 8, 3} 35 medians := PrefixMedian(arr) 36 fmt.Println("Final Medians Array:") 37 for _, median := range medians { 38 fmt.Printf("%d ", median) 39 } 40}
Lesson Summary

Congratulations! You've tackled an algorithmic challenge using Go's container/heap package to handle heaps efficiently. This approach not only enhances your understanding of Go's powerful built-in packages but also strengthens your skills in algorithmic problem-solving. Keep practicing with similar problems to master data-structure management and efficient coding techniques in Go. Happy coding!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.