GuilinDev

Lc0347

05 August 2008

347 - Top K Frequent Elements

原题概述

Given a non-empty array of integers, return the k most frequent elements.

For example,
Given

1
[1,1,1,2,2,3]
and k = 2, return
1
[1,2]
.

Note:

  • You may assume k is always valid, 1 ≤ k ≤ number of unique elements.
  • Your algorithm’s time complexity must be better than O(n log n), where n is the array’s size.

题意和分析

高频题,给一个非空的整数数组和一个总是有效的的整数k,返回前k个最多出现的元素。可以用HashMap来做桶排序,TreeMap和最大堆来做。

代码

用最大堆,把数字和其频率放到HashMap中,然后把map的entry放到maxHeap中,从最顶上拿走k个数字的entry然后相对应的keys就是最高频率的k个数字。

时间复杂度:O(nlogk),n 表示数组的长度。首先,遍历一遍数组统计元素的频率,这一系列操作的时间复杂度是 O(n);接着,遍历用于存储元素频率的 map,如果元素的频率大于最小堆中顶部的元素,则将顶部的元素删除并将该元素加入堆中,这里维护堆的数目是 k,所以这一系列操作的时间复杂度是 O(nlogk) 的;因此,总的时间复杂度是 O(nlogk)。

空间复杂度:O(n),最坏情况下(每个元素都不同),map 需要存储 n 个键值对,优先队列需要存储 k 个元素,因此,空间复杂度是 O(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
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);//原数组的元素为key,出现次数为value
        }

        //利用最大堆
        PriorityQueue<Map.Entry<Integer, Integer>> maxHeap = new PriorityQueue<>(
                (a, b) -> (b.getValue() - a.getValue())
        );

        maxHeap.addAll(map.entrySet());

        int[] result = new int[k];
        int index = 0;
        while (index < k) {
            Map.Entry<Integer, Integer> entry = maxHeap.poll();//取出顶部元素并删除
            result[index] = entry.getKey();
            index++;
        }
        return result;
    }
}

同理,用最小堆也可以

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
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();

        // hashmap统计出现次数
        for(int num: nums){
            map.put(num, map.getOrDefault(num,0) + 1);
        }

        // 用最小堆准备找到k个
        PriorityQueue<Map.Entry<Integer, Integer>> minHeap =
                new PriorityQueue<>(Comparator.comparingInt(Map.Entry::getValue));

        // 遍历hashmap
        for(Map.Entry<Integer,Integer> entry: map.entrySet()){
            minHeap.add(entry);
            if (minHeap.size() > k) {
                minHeap.poll();
            }
        }

        // 找个k个最多出现的值
        int[] result = new int[k];
        for (int i = 0; i < k; i++) {
            result[i] = minHeap.poll().getKey();
        }
      
        return result;
    }
}

基于快速排序,我们可以使用基于快速排序的方法,求出「出现次数数组」的前 k 大的值。

在对数组arr[l…r]做快速排序的过程中,我们首先将数组划分为两个部分arr[i…q−1]与arr[q+1…j],并使得arr[i…q−1] 中的每一个值都不超过arr[q],且arr[q+1…j] 中的每一个值都大于arr[q]。

于是,根据 k 与左侧子数组arr[i…q−1] 的长度(为q−i)的大小关系:

如果k≤q−i,则数组arr[l…r] 前 k 大的值,就等于子数组arr[i…q−1] 前 k 大的值。 否则,数组arr[l…r] 前 k 大的值,就等于左侧子数组全部元素,加上右侧子数组arr[q+1…j] 中前k−(q−i) 大的值。

原版的快速排序算法的平均时间复杂度为 O(NlogN)。我们的算法中,每次只需在其中的一个分支递归即可,因此算法的平均时间复杂度降为 O(N)。

时间复杂度:O(N^2),其中 N 为数组的长度

空间复杂度:O(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
31
32
33
34
35
36
37
38
39
40
41
42
43
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> occurrences = new HashMap<Integer, Integer>();
        for (int num : nums) {
            occurrences.put(num, occurrences.getOrDefault(num, 0) + 1);
        }

        List<int[]> values = new ArrayList<int[]>();
        for (Map.Entry<Integer, Integer> entry : occurrences.entrySet()) {
            int num = entry.getKey(), count = entry.getValue();
            values.add(new int[]{num, count});
        }
        int[] ret = new int[k];
        qsort(values, 0, values.size() - 1, ret, 0, k);
        return ret;
    }

    public void qsort(List<int[]> values, int start, int end, int[] ret, int retIndex, int k) {
        int picked = (int) (Math.random() * (end - start + 1)) + start;
        Collections.swap(values, picked, start);
        
        int pivot = values.get(start)[1];
        int index = start;
        for (int i = start + 1; i <= end; i++) {
            if (values.get(i)[1] >= pivot) {
                Collections.swap(values, index + 1, i);
                index++;
            }
        }
        Collections.swap(values, start, index);

        if (k <= index - start) {
            qsort(values, start, index - 1, ret, retIndex, k);
        } else {
            for (int i = start; i <= index; i++) {
                ret[retIndex++] = values.get(i)[0];
            }
            if (k > index - start + 1) {
                qsort(values, index + 1, end, ret, retIndex, k - (index - start + 1));
            }
        }
    }
}

HashMap桶排序的做法,在建立好数字和其出现次数的映射后利用桶排序取k个最频繁的数字,时空均为O(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
class Solution {    
    public List<Integer> topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);//原数组的元素为key,出现次数为value
        }

        //创建一个放buckets的list
        List<Integer>[] bucket = new List[nums.length + 1];//如果只有一个元素,那么也需要一个index为1的bucket
        for (int num : map.keySet()) {
            int freq = map.get(num);
            if (bucket[freq] == null) {//没有这个桶,就加一个
                bucket[freq] = new LinkedList<>();
            }
            bucket[freq].add(num);//在每个桶加上各自的元素
        }

        List<Integer> result = new LinkedList<>();
        for (int i = bucket.length - 1; i > 0 && k > 0; i--) {//从做到右取n个
            if (bucket[i] != null) {//只要出现过
                List<Integer> list = bucket[i];
                result.addAll(list);
                k -= list.size();
            }
        }
        return result;
    }
}

利用TreeMap自动排序

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
class Solution {
    public List<Integer> topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);//原数组的元素为key,出现次数为value
        }

        //利用treeMap自动排序
        TreeMap<Integer, List<Integer>> freqMap = new TreeMap<>();
        for (int num : map.keySet()) {
            int freq = map.get(num);
            if (!freqMap.containsKey(freq)) {
                freqMap.put(freq, new LinkedList<>());
            }
            freqMap.get(freq).add(num);
        }

        List<Integer> result = new ArrayList<>();
        while (result.size() < k) {//取前k位
            Map.Entry<Integer, List<Integer>> entry = freqMap.pollLastEntry();
            result.addAll(entry.getValue());
        }
        return result;
    }
}