LeetCode 347. 前K个高频元素

题目描述

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

哈希表 + 堆排

先用哈希表进行频率统计,然后维护一个大小为k的小根堆,每次尝试往堆中插入一个元素。
若堆的大小不足k,则直接插入。否则,与堆顶比较,若堆顶比当前元素大,直接舍弃当前元素(堆顶已经是k个中最小的了,当前元素更小,则当前元素不肯能是前k大);若堆顶比当前元素小,则用当前元素替换堆顶,并向下调整

/**
* 11ms
**/
class Solution {
    
    int[] heapVal;
    int[] heapFreq;
    int heapSize;
    int heapSizeLimit;

    public int[] topKFrequent(int[] nums, int k) {
        heapVal = new int[k];
        heapFreq = new int[k];
        heapSize = 0;
        heapSizeLimit = k;
        // 堆中下标0~k-1, 
        // 小根堆
        Map<Integer, Integer> freq = new HashMap<>();
        for (int i : nums) freq.put(i, freq.getOrDefault(i, 0) + 1);
        for (int key : freq.keySet()) insert(key, freq.get(key));
        return heapVal;
    }

    private void insert(int val, int freq) {
        if (heapSize == heapSizeLimit) {
            // 已经有k个元素在堆里了, 直接比较堆顶
            if (heapFreq[0] >= freq) return ; // 堆顶比当前元素大, 则当前元素无需插入
            // 堆顶比当前元素小, 则需要替换掉堆顶
            heapVal[0] = val;
            heapFreq[0] = freq;
            down(0);
            return ;
        }
        // 堆里不足k个元素, 则直接插入到末尾, 并向上调整
        heapVal[heapSize] = val;
        heapFreq[heapSize] = freq;
        up(heapSize);
        heapSize++;
    }

    private void up(int i) {
        while (i > 0 && heapFreq[(i - 1) / 2] > heapFreq[i]) {
            swap((i - 1) / 2, i);
            i = (i - 1) / 2;
        }
    }

    private void down(int i) {
        int min = i;
        if (2 * i + 1 < heapSize && heapFreq[2 * i + 1] < heapFreq[min]) min = 2 * i + 1;
        if (2 * i + 2 < heapSize && heapFreq[2 * i + 2] < heapFreq[min]) min = 2 * i + 2;
        if (min != i) {
            swap(min, i);
            down(min);
        }
    }

    private void swap(int i, int j) {
        int t = heapVal[i];
        heapVal[i] = heapVal[j];
        heapVal[j] = t;

        t = heapFreq[i];
        heapFreq[i] = heapFreq[j];
        heapFreq[j] = t;
    }
}

哈希表 + 快排

同样先用哈希表进行频率统计。然后生成两个数组val[]freq[],分别存储值,以及值出现的频率。
随后手写一个快排,按照freq[]从大到小排序。
每趟排序,都会将数组分为左侧较大的,和右侧较小的两部分。
每次查看左侧部分的长度leftLen

  • leftLen > k, 则只需要递归的对左侧部分进行处理。
  • leftLen = k, 直接结束。
  • leftLen < k, 则左侧一定是前k大, 随后对右侧进行处理, 并传入 k = k - leftLen
    随后取val[]的前k项, 作为答案返回即可
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer, Integer> map = new HashMap<>();
        for (int i : nums) map.put(i, map.getOrDefault(i, 0) + 1);
        int n = map.size();
        int[] val = new int[n];
        int[] freq = new int[n];
        int i = 0;
        for (int key : map.keySet()) {
            val[i] = key;
            freq[i] = map.get(key);
            // System.out.printf("val[%d] = %d, freq[%d] = %d\n", i, val[i], i, freq[i]);
            i++;
        }
        quickSort(val, freq, 0, n - 1, k);
        int[] ret = new int[k];
        for (i = 0; i < k; i++) ret[i] = val[i];
        return ret;
    }

    // 按照freq从大到小排序
    private void quickSort(int[] val, int[] freq, int l, int r, int k) {
        if (l >= r) return ;
        int x = freq[l + r >> 1], i = l - 1, j = r + 1;
        while (i < j) {
            do i++; while (freq[i] > x);
            do j--; while (freq[j] < x);
            if (i < j) {
                int t = freq[i];
                freq[i] = freq[j];
                freq[j] = t;
                t = val[i];
                val[i] = val[j];
                val[j] = t;
            }
        }
        int left = j - l + 1;
        if (left == k) return ;
        if (left > k) quickSort(val, freq, l, j, k);
        if (left < k) quickSort(val, freq, j + 1, r, k - left);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值