题目描述:
给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例1:
1 2
| 输入: nums = [1,1,1,2,2,3], k = 2 输出: [1,2]
|
示例2:
1 2
| 输入: nums = [1], k = 1 输出: [1]
|
说明:你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。你的算法的时间复杂度必须优于 $O(n log n)$, n 是数组的大小。
解法一:小顶堆
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| import java.util.*; public class Solution { public static void main(String[] args) { Solution solution = new Solution(); int[] nums1 = {1,1,1,2,2,3}; int k = 2; System.out.println(solution.topKFrequent(nums1, k)); } public List<Integer> topKFrequent(int[] nums, int k) { HashMap<Integer, Integer> count = new HashMap(); for (int n: nums) { count.put(n, count.getOrDefault(n, 0) + 1); } PriorityQueue<Integer> heap = new PriorityQueue<Integer>((n1, n2) -> count.get(n1) - count.get(n2)); for (int n: count.keySet()) { heap.add(n); if (heap.size() > k) { heap.poll(); } } List<Integer> top_k = new LinkedList(); while (!heap.isEmpty()) { top_k.add(heap.poll()); } Collections.reverse(top_k); return top_k; } }
|
复杂度分析
时间复杂度:$O(Nlogk)$。遍历一遍数组统计元素的频率的时间复杂度是 O(N),建堆和输出的复杂度是 O(Nlog(k))。因此总复杂度为 O(N+Nlog(k))=O(Nlog(k))。
空间复杂度:$O(N)$,存储哈希表的开销。最坏情况下(每个元素都不同),map 需要存储 n 个键值对,优先队列需要存储 k 个元素,因此,空间复杂度是 O(n)。根据复杂度分析,方法对于小 k 的情况是很优的。但是如果 k 值很大,我们可以将算法改成删除频率最低的若干个元素。
解法二:桶排序法
首先依旧使用哈希表统计频率,统计完成后,创建一个数组,将频率作为数组下标,对于出现频率不同的数字集合,存入对应的数组下标即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| import java.util.*; public class Solution { public static void main(String[] args) { Solution solution = new Solution(); int[] nums1 = {1,1,1,2,2,3}; int k = 2; System.out.println(solution.topKFrequent(nums1, k)); //[1, 2] } public List<Integer> topKFrequent(int[] nums, int k) { List<Integer> res = new ArrayList(); //返回值res是一个数组,其长度等于k // 使用哈希表(字典),统计每个元素出现的次数,元素为键,元素出现的次数为值 HashMap<Integer,Integer> map = new HashMap(); for(int num : nums){ if (map.containsKey(num)) { map.put(num, map.get(num) + 1); } else { map.put(num, 1); } } //桶排序:获取出现的次数作为下标 //将频率作为数组下标,对于出现频率不同的数字集合,存入对应的数组下标 List<Integer>[] list = new List[nums.length+1]; //list最大的长度是当输入的所有数字都一样时的长度加1 for(int key : map.keySet()){ int i = map.get(key); // 获取出现的次数作为下标 if(list[i] == null){ list[i] = new ArrayList(); //这里是一个数组,因为可能有多个数字出现的次数是一样的 } list[i].add(key); } // 倒序遍历数组获取出现顺序从大到小的排列 for(int i = list.length - 1;i >= 0 && res.size() < k;i--){ //返回值res是一个数组,其长度等于k if(list[i] == null) continue; res.addAll(list[i]); //存入的是一个数组,所以用addAll()而非add() } return res; } }
|
复杂度分析
时间复杂度:O(n) ,n 表示数组的长度。首先,遍历一遍数组统计元素的频率,这一系列操作的时间复杂度是 O(n);桶的数量为 n+1,所以桶排序的时间复杂度为 O(n);因此,总的时间复杂度是 O(n)。
空间复杂度:O(n) 。