每日算法:前 K 个高频元素

开发 前端 算法
桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。

 

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

示例 1:

  1. 输入: nums = [1,1,1,2,2,3], k = 2 
  2. 输出: [1,2] 

示例 2:

  1. 输入: nums = [1], k = 1 
  2. 输出: [1] 

提示:

  • 你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
  • 你的算法的时间复杂度必须优于 O(nlogn) , n 是数组的大小。
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的。
  • 你可以按任意顺序返回答案。

解法一:map+数组

利用 map 记录每个元素出现的频率,利用数组来比较排序元素

代码实现:

  1. let topKFrequent = function(nums, k) { 
  2.     let map = new Map(), arr = [...new Set(nums)] 
  3.     nums.map((num) => { 
  4.         if(map.has(num)) map.set(num, map.get(num)+1) 
  5.         else map.set(num, 1) 
  6.     }) 
  7.      
  8.     return arr.sort((a, b) => map.get(b) - map.get(a)).slice(0, k); 
  9. }; 

复杂度分析:

  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(n)

题目要求算法的时间复杂度必须优于 O(n log n) ,所以这种实现不合题目要求

解法二:map + 小顶堆

遍历一遍数组统计每个元素的频率,并将元素值( key )与出现的频率( value )保存到 map 中

通过 map 数据构建一个前 k 个高频元素小顶堆,小顶堆上的任意节点值都必须小于等于其左右子节点值,即堆顶是最小值。

具体步骤如下:

  • 遍历数据,统计每个元素的频率,并将元素值( key )与出现的频率( value )保存到 map 中
  • 遍历 map ,将前 k 个数,构造一个小顶堆
  • 从 k 位开始,继续遍历 map ,每一个数据出现频率都和小顶堆的堆顶元素出现频率进行比较,如果小于堆顶元素,则不做任何处理,继续遍历下一元素;如果大于堆顶元素,则将这个元素替换掉堆顶元素,然后再堆化成一个小顶堆。
  • 遍历完成后,堆中的数据就是前 k 大的数据

代码实现:

  1. let topKFrequent = function(nums, k) { 
  2.     let map = new Map(), heap = [,] 
  3.     nums.map((num) => { 
  4.         if(map.has(num)) map.set(num, map.get(num)+1) 
  5.         else map.set(num, 1) 
  6.     }) 
  7.      
  8.     // 如果元素数量小于等于 k 
  9.     if(map.size <= k) { 
  10.         return [...map.keys()] 
  11.     } 
  12.      
  13.     // 如果元素数量大于 k,遍历map,构建小顶堆 
  14.     let i = 0 
  15.     map.forEach((value, key) => { 
  16.         if(i < k) { 
  17.             // 取前k个建堆, 插入堆 
  18.             heap.push(key
  19.             // 原地建立前 k 堆 
  20.             if(i === k-1) buildHeap(heap, map, k) 
  21.         } else if(map.get(heap[1]) < value) { 
  22.             // 替换并堆化 
  23.             heap[1] = key 
  24.             // 自上而下式堆化第一个元素 
  25.             heapify(heap, map, k, 1) 
  26.         } 
  27.         i++ 
  28.     }) 
  29.     // 删除heap中第一个元素 
  30.     heap.shift() 
  31.     return heap 
  32. }; 
  33.  
  34. // 原地建堆,从后往前,自上而下式建小顶堆 
  35. let buildHeap = (heap, map, k) => { 
  36.     if(k === 1) return 
  37.     // 从最后一个非叶子节点开始,自上而下式堆化 
  38.     for(let i = Math.floor(k/2); i>=1 ; i--) { 
  39.         heapify(heap, map, k, i) 
  40.     } 
  41.  
  42. // 堆化 
  43. let heapify = (heap, map, k, i) => { 
  44.     // 自上而下式堆化 
  45.     while(true) { 
  46.         let minIndex = i 
  47.         if(2*i <= k && map.get(heap[2*i]) < map.get(heap[i])) { 
  48.             minIndex = 2*i 
  49.         } 
  50.         if(2*i+1 <= k && map.get(heap[2*i+1]) < map.get(heap[minIndex])) { 
  51.             minIndex = 2*i+1 
  52.         } 
  53.         if(minIndex !== i) { 
  54.             swap(heap, i, minIndex) 
  55.             i = minIndex 
  56.         } else { 
  57.             break 
  58.         } 
  59.     } 
  60.  
  61. // 交换 
  62. let swap = (arr, i , j) => { 
  63.     let temp = arr[i] 
  64.     arr[i] = arr[j] 
  65.     arr[j] = temp 

复杂度分析:

  • 时间复杂度:遍历数组需要 O(n) 的时间复杂度,一次堆化需要 O(logk) 时间复杂度,所以利用堆求 Top k 问题的时间复杂度为 O(nlogk)
  • 空间复杂度:O(n)

解法三:桶排序

这里取前k个高频元素,使用计数排序不再适合,在上题目中使用计数排序,将 i 元素出现的次数存储在 bucket[i] ,但这种存储不能保证 bucket 数组上值是有序的,例如 bucket=[0,3,1,2] ,即元素 0 未出现,元素 1 出现 3 次,元素 2 出现 1 次,元素 3 出现 2 次,所以计数排序不适用于取前k个高频元素,不过,不用怕,计数排序不行,还有桶排序。

桶排序是计数排序的升级版。它也是利用函数的映射关系。

桶排序 (Bucket sort)的工作的原理:假设输入数据服从均匀分布,将数据分到有限数量的桶里,每个桶再分别排序(有可能再使用别的排序算法或是以递归方式继续使用桶排序进行排)。

  • 首先使用 map 来存储频率
  • 然后创建一个数组(有数量的桶),将频率作为数组下标,对于出现频率不同的数字集合,存入对应的数组下标(桶内)即可。

代码实现:

  1. let topKFrequent = function(nums, k) { 
  2.     let map = new Map(), arr = [...new Set(nums)] 
  3.     nums.map((num) => { 
  4.         if(map.has(num)) map.set(num, map.get(num)+1) 
  5.         else map.set(num, 1) 
  6.     }) 
  7.      
  8.     // 如果元素数量小于等于 k 
  9.     if(map.size <= k) { 
  10.         return [...map.keys()] 
  11.     } 
  12.      
  13.     return bucketSort(map, k) 
  14. }; 
  15.  
  16. // 桶排序 
  17. let bucketSort = (map, k) => { 
  18.     let arr = [], res = [] 
  19.     map.forEach((value, key) => { 
  20.         // 利用映射关系(出现频率作为下标)将数据分配到各个桶中 
  21.         if(!arr[value]) { 
  22.             arr[value] = [key
  23.         } else { 
  24.             arr[value].push(key
  25.         } 
  26.     }) 
  27.     // 倒序遍历获取出现频率最大的前k个数 
  28.     for(let i = arr.length - 1;i >= 0 && res.length < k;i--){ 
  29.         if(arr[i]) { 
  30.             res.push(...arr[i]) 
  31.         } 
  32.  } 
  33.  return res 

复杂度分析:

  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

leetcode:https://leetcode-cn.com/problems/top-k-frequent-elements/solution/javascript-qian-k-ge-gao-pin-yuan-su-by-user7746o/ 

责任编辑:武晓燕 来源: 三分钟学前端
相关推荐

2021-09-08 09:52:34

语言

2021-10-29 07:25:32

螺旋矩阵整数

2021-08-30 14:34:10

有效算法字符

2021-10-28 19:33:36

矩阵图像内存

2020-08-16 12:38:32

Python算法编程

2018-11-08 16:18:07

JavaScript前端

2021-11-19 07:54:40

前端

2021-11-12 09:44:03

字符串算法复杂度

2021-09-30 09:58:14

路径总和二叉树

2021-11-04 09:59:03

动态规划策略

2021-09-03 09:41:36

字符串时间复杂度

2021-12-07 06:55:17

节流函数Throttle

2014-11-28 16:08:33

射频识别RFID

2021-12-09 10:57:19

防抖函数 Debounce

2021-10-27 10:43:36

数据流中位数偶数

2021-09-29 10:19:00

算法平衡二叉树

2021-09-02 09:22:13

算法无重复字符

2024-05-27 00:05:00

2022-12-06 09:44:00

算法神经网络

2021-11-02 10:10:38

面试元素语言
点赞
收藏

51CTO技术栈公众号