Top K 问题相信大家在面试过程中,经常被问到,下面就和大家一起探讨下两种常见的算法实现。
一. 基于堆排序实现
思路基于数组的前K个数,生成一个小顶堆,数组剩余元素依次与堆顶数据比较,小于等于堆顶数据时直接舍弃,大于堆顶数据时替换掉堆顶数据,并调整堆结构保证满足小顶堆要求。算法优势
时间复杂度是O(N*logK),不需要将数组一次全部加载到内存中,可以处理海量数据。图解
- 代码实现(Golang)
// TopK-堆排序法func TopKByMinHeap(nums []int, k int) []int { length := len(nums) // 数组长度小于k,直接返回 if length < k { return nums } // 取出数组前k个数据,并生成小顶堆 minHeap := make([]int, 0) minHeap = append(minHeap, nums[:k]...) // 前k个数据生成小顶堆 CreateMinHeap(minHeap) // 遍历数组剩余数据,大于堆顶数据时,替换堆顶,重新维护小顶堆 for i := k; i < length; i++ { if nums[i] > minHeap[0] { minHeap[0] = nums[i] MinHeapify(minHeap, 0, k) } } return minHeap}// 自底向上创建小顶堆func CreateMinHeap(nums []int) { length := len(nums) for i := length - 1; i >= 0; i-- { MinHeapify(nums, i, length) }}// 维护小顶堆func MinHeapify(nums []int, posIndex, length int) { // 堆左孩子节点index leftIndex := 2*posIndex + 1 // 堆右孩子节点index rightIndex := 2*posIndex + 2 // 当前节点以及左右孩子节点中最小值的index, 初始化为当前节点index minIndex := posIndex // 左孩子存在并且小于当前节点值时, 最小值index替换为左孩子index if leftIndex < length && nums[leftIndex] < nums[minIndex] { minIndex = leftIndex } // 右孩子存在并且小于当前节点值时, 最小值index替换为右孩子index if rightIndex < length && nums[rightIndex] < nums[minIndex] { minIndex = rightIndex } // 最小值节点index不等于当前节点时,替换当前节点和其中较小孩子节点值 if minIndex != posIndex { nums[posIndex], nums[minIndex] = nums[minIndex], nums[posIndex] MinHeapify(nums, minIndex, length) }}
二. 基于快排实现
- 思路
利用快排的分划函数找到分界点位置K,则前K个数据即所求结果。 - 算法优势
时间复杂度是O(N),对于可以一次性加载到内存的数组效率很高。 - 图解
- 代码实现(Golang)
// TopK-快速排序法func TopKByQuickSort(nums []int, k int) []int { length := len(nums) // 数组长度小于k,直接返回 if length < k { return nums } // 数组进行快排,左侧边界 left := 0 // 数组进行快排,右侧边界 right := length // 第一次快排后,获取分界点index pivotIndex := partition(nums, left, right) // 循环快排,找到分界点index刚好等于k for pivotIndex != k { if pivotIndex < k { // 分界点index小于k,继续对分界点右侧进行快排,重新获取分界点index left = pivotIndex + 1 } else { // 分界点index大于k,缩小快排范围为上次分界点与本次分界点之间数据,重新获取分界点index right = pivotIndex } pivotIndex = partition(nums, left, right) } // 取前k个有序数据 return nums[:k]}// 按分界点,进行快排,并返回分界点indexfunc partition(nums []int, left, right int) int { // 初始化分界值为左边界值 pivot := nums[left] // 所有大于分界值的数据边界index pos := left // 小于分界值时,边界扩展,将数据替换到边界值index位置, for i := left; i < right; i++ { if nums[i] > pivot { pos++ nums[i], nums[pos] = nums[pos], nums[i] } } // 交换分界值的数据边界index和分界点index,使得分界点左侧均大于分界点,右侧均小于分界点 nums[pos], nums[left] = nums[left], nums[pos] return pos}