leetcode *347. 前 K 个高频元素(堆排序)

【题目】*347. 前 K 个高频元素

剑指 Offer 40. 最小的k个数
*215. 数组中的第K个最大元素
*347. 前 K 个高频元素

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

示例 1:

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

示例 2:

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

提示:

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

【解题思路1】map、小顶堆

用map记录各个元素出现的次数,然后排序后得到频率最高的k个数

class Solution {
    
    
    public int[] topKFrequent(int[] nums, int k) {
    
    
        Map<Integer, Integer> map = new HashMap<>();
        for(int temp : nums) {
    
    
            map.put(temp, map.getOrDefault(temp, 0) + 1);
        }
        int len = map.size();
        int[][] order = new int[len][2];
        int index = 0;
        for(int temp : map.keySet()) {
    
    
            order[index][0] = temp;
            order[index][1] = map.get(temp);
            index++;
        }
        Arrays.sort(order, (a, b) -> b[1] - a[1]);
        int[] ans = new int[k];
        for(int i = 0; i < k; i++) {
    
    
            ans[i] = order[i][0];
        }
        return ans;
    }
}

最简单的做法是给「出现次数数组」排序。但由于可能有 O(N) 个不同的出现次数,故总的算法复杂度会达到 O(NlogN),不满足题目的要求。
可以利用堆的思想改进:维护一个大小为k的小顶堆,然后遍历「出现次数数组」

  • 如果堆的元素个数小于 k,就可以直接插入堆中。
  • 如果堆的元素个数等于 k,则检查堆顶与当前出现次数的大小。如果堆顶更大,说明至少有 k 个数字的出现次数比当前值大,故舍弃当前值;否则,就弹出堆顶,并将当前值插入堆中。

遍历完成后,堆中的元素就代表了「出现次数数组」中前 k 大的值。
下面是使用优先队列这个现成的类型实现的,也可以手动实现小顶堆

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);
        }

        // int[] 的第一个元素代表数组的值,第二个元素代表了该值出现的次数
        PriorityQueue<int[]> queue = new PriorityQueue<int[]>(new Comparator<int[]>() {
    
    
            public int compare(int[] m, int[] n) {
    
    
                return m[1] - n[1];
            }
        });
        for (Map.Entry<Integer, Integer> entry : occurrences.entrySet()) {
    
    
            int num = entry.getKey(), count = entry.getValue();
            if (queue.size() == k) {
    
    
                if (queue.peek()[1] < count) {
    
    
                    queue.poll();
                    queue.offer(new int[]{
    
    num, count});
                }
            } else {
    
    
                queue.offer(new int[]{
    
    num, count});
            }
        }
        int[] ret = new int[k];
        for (int i = 0; i < k; ++i) {
    
    
            ret[i] = queue.poll()[0];
        }
        return ret;
    }
}
class Solution {
    
    
    public int[] topKFrequent(int[] nums, int k) {
    
    
        Map<Integer, Integer> map = new HashMap<>();
        for (int n : nums){
    
    
            map.put(n, map.getOrDefault(n, 0) + 1);
        }
        PriorityQueue<Map.Entry<Integer, Integer>> queue = new PriorityQueue<>((e1, e2) -> e2.getValue() - e1.getValue());
        queue.addAll(map.entrySet());
        int[] ans = new int[k];
        for (int i = 0; i < k && !queue.isEmpty(); ++i){
    
    
            ans[i] = queue.poll().getKey();
        }
        return ans;
    }
}

手动建堆

class Solution {
    
    
    //记录数组中元素及其对应出现的次数
    Map<Integer , Integer> map = new HashMap<>();
    
    public int[] topKFrequent(int[] nums, int k) {
    
        
        int[] heap = new int[k];
        for(int i : nums){
    
    
            map.put(i , map.getOrDefault(i,0) + 1);
        }
        Iterator it = map.keySet().iterator();
        int i = 0;
        while(it.hasNext()){
    
    
            // 先初始化一个大小为k的堆,当堆中元素个数为k时,建立小根堆
            if( i < k){
    
    
                heap[i] = (Integer)it.next();
                i++;
                if(i == k){
    
    
                    for(int j = k/2 -1 ; j >= 0 ; j--){
    
    
                        heapSort(heap,j);
                    }
                }
            }else{
    
       //小根堆建好后 ,对于每个新遍历的元素与堆顶比较,如果比堆顶大,替换堆顶,重新维持小根堆
                int key = (Integer)it.next(); 
                if(map.get(key) > map.get(heap[0])) heap[0] = key;
                heapSort(heap , 0);
            }
            
        }
        return heap;
    }
    
    //维持小根堆
    //注意,堆中元素比较是比较它们出现的次数,即该元素在map中对应的value
    public void heapSort(int[] heap , int i){
    
    
        int temp = heap[i];
        for(int j = 2*i + 1 ; j < heap.length ; j = 2*j + 1){
    
    
            if(j+1 < heap.length && map.get(heap[j+1]) < map.get(heap[j])) j++;
            if(map.get(heap[j]) < map.get(temp)){
    
    
                heap[i] = heap[j];
                i = j;
            }else break;
        }
        heap[i] = temp;
    }
}

【解题思路2】快排(待研究)

在对数组 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)的大小关系:

扫描二维码关注公众号,回复: 11650082 查看本文章
  • 如果 k≤q−i,则数组 arr[l…r] 前 k 大的值,就等于子数组 arr[i…q−1] 前 k 大的值。
  • 否则,数组 arr[l…r] 前 k 大的值,就等于左侧子数组全部元素,加上右侧子数组 arr[q+1…j] 中前 k−(q−i) 大的值。
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));
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/XunCiy/article/details/108441826