记录 1 道算法题
前 K 个高频元素
前 K 个涉及道排序的问题,题目没有要求结果一定是升序或降序,所以可以用堆或快排进行解决。
首先遍历一遍数组用一个对象记录每个元素出现的次数。然后将这个对象转换成二维数组进行存储排序。如果是堆的话,直接把堆的结果输出就行。如果是快排则需要截取数组的前 k 个。
- 堆
堆采用小顶堆,即堆顶是堆内最小元素,堆底是堆内最大元素
堆的实现就不赘诉了,可以看这篇文章
function topKFrequent(nums, k) {
const map = {}
// 收集每个元素出现的次数
for (let i = 0; i < nums.length; i++) {
let item = nums[i]
if (map[item]) {
map[item]++
} else {
map[item] = 1
}
}
const heap = new Heap((a, b) => a[1] - b[1])
nums = Object.entries(map)
for (let i = 0; i < nums.length; i++) {
const item = nums[i]
// 排除掉比堆顶还要小的元素
if (heap.size() > k && item[1] < heap.data[0][1]) return
// 存入堆中
heap.push(item)
if (heap.size() > k) {
heap.pop()
}
}
// 因为结果只要元素,所以要对二维数组进行处理
return heap.data.reduce((a, b) => {
a.push(+b[0])
return a
}, [])
}
- 快排
提供记录元素出现次数的数组和某一段的开始下标和结束下标,当比较的基准点的下标是小于 k 的时候,说明我们想要的前 k 个元素在左边。于是递归处理左边,直到基准点 等于 k。这时候数组 0-k 就是我们需要的前 K 个高频元素,而且是按出现次数降序的。
快排的实现可以看这篇文章
function topKFrequent(nums, k) {
const map = {}
for (let i = 0; i < nums.length; i++) {
let item = nums[i]
if (map[item]) {
map[item]++
} else {
map[item] = 1
}
}
nums = Object.entries(map)
// 开始排序
fastsort(nums, 0, nums.length - 1, k)
// 快排是对整个数组原地排序,所以要截取前 k 个
return nums.slice(0, k).reduce((a, b) => {
a.push(+b[0])
return a
}, [])
}
function fastsort(nums, start, end, k) {
默认用每一段的第一个元素作为基准点
let pivot = nums[start]
let left = start + 1
// 然后从第二个开始进行归类。
// 因为想要降序,所以分为比基准点大的放在左边,比基准点小的放在右边
for (let i = left; i <= end; i++) {
let item = nums[i]
if (item[1] > pivot[1]) {
swap(nums, i, left)
left++
}
}
// 分类完之后, left 是右边的开始下标,所以 left - 1 就是左边的结束下标,
// 和基准点进行交换,这样 left - 1 就是基准点下标
swap(nums, start, left - 1)
// 如果基准点下标是 k - 1 则说明前 k 个以及确定了。
// 如果不是则说明还没排序完,因为可能是 [10] 5 [2,3,4] 或者 [4, 6, 7] 3 [1, 2]
// 这样如果是想要前 3 个 或 前 2 个的时候,都是不准确的。
if (left > k) {
fastsort(nums, start, left - 1, k)
} else if (left === k) {
return
} else {
fastsort(nums, left, end, k)
}
}
function swap(arr, a, b) {
;[arr[a], arr[b]] = [arr[b], arr[a]]
}
结束