347. Top K Frequent Elements
Intuition
The problem asks us to find the k most frequent elements in an array. The key insight is that we need to:
- Count the frequency of each element
- Find the k elements with the highest frequencies
A heap (priority queue) is perfect for this task because it allows us to efficiently maintain the top k elements based on their frequencies.
Approach
- Count Frequencies: Use a hash map to count the frequency of each element in the input array.
- Build Max Heap: Create a max heap where elements are ordered by their frequencies. We implement a custom heap data structure with:
- A comparator function to define the heap property
- Standard heap operations: Push, Pop, Top
- Heap maintenance through up-heap and down-heap operations
- Extract Top K: Push all unique elements with their frequencies into the heap, then pop k times to get the k most frequent elements.
The custom heap implementation uses 1-indexed storage for easier parent-child calculations:
- Parent of node i: i/2
- Left child of node i: 2*i
- Right child of node i: 2*i+1
Complexity
- Time complexity: O(n log n)
- Space complexity: O(n)
Keywords
- Hash Map
- Heap (Priority Queue)
- Frequency Counting
- Top K Problems
- Custom Data Structure
Code
type Comparator func(child, parent interface{}) bool
type heap struct {
Storage []interface{}
CmpFunc Comparator
}
func NewHeap(cmpFunc Comparator) *heap {
return &heap {
Storage: append(make([]interface{}, 0), -1),
CmpFunc: cmpFunc,
}
}
func (h *heap) Len() int {
return len(h.Storage) - 1
}
func (h *heap) IsEmpty() bool {
return h.Len() == 0
}
func (h *heap) cmp(child, parent interface{}) bool {
return h.CmpFunc(child, parent)
}
func (h *heap) swap(x, y int) {
h.Storage[x], h.Storage[y] = h.Storage[y], h.Storage[x]
}
func (h *heap) Top() (interface{}, error) {
if h.IsEmpty() {
return nil, errors.New("Heap is empty.")
}
return h.Storage[1], nil
}
func (h *heap) Push(item interface{}) {
h.Storage = append(h.Storage, item)
now := h.Len()
for now / 2 > 0 && !h.cmp(h.Storage[now], h.Storage[now / 2]) {
h.swap(now, now / 2)
now /= 2
}
}
func (h *heap) Pop() (interface{}, error) {
top, err := h.Top()
if err != nil {
return nil, err
}
last := h.Len()
h.swap(1, last)
h.Storage = h.Storage[: last]
now := 1
for now < last {
left, right := 0, 0
if now * 2 < last && !h.cmp(h.Storage[now * 2], h.Storage[now]) {
left = now * 2
}
if now * 2 + 1 < last && !h.cmp(h.Storage[now * 2 + 1], h.Storage[now]) {
right = now * 2 + 1
}
if left == 0 && right == 0 {
break
} else if left != 0 && right == 0 {
h.swap(now, left)
now = left
} else if left == 0 && right != 0 {
h.swap(now, right)
now = right
} else {
if h.cmp(h.Storage[left], h.Storage[right]) {
h.swap(now, right)
now = right
} else {
h.swap(now, left)
now = left
}
}
}
return top, nil
}
func topKFrequent(nums []int, k int) []int {
cnt := make(map[int]int)
for _, num := range nums {
cnt[num] += 1
}
type unit struct {
num, freq int
}
cmpFunc := func(child, parent interface{}) bool {
return child.(unit).freq < parent.(unit).freq
}
hp, ret := NewHeap(cmpFunc), make([]int, 0)
for key, val := range cnt {
hp.Push(unit{key, val})
}
for k > 0 {
top, _ := hp.Pop()
ret = append(ret, top.(unit).num)
k -= 1
}
return ret
}