查找热点数据问题 | 2024 刷题 掘金 MarsCode 困难题

43 阅读4分钟

问题描述

给你一个整数数组 nums 和一个整数 k,请你用一个字符串返回其中出现频率前 k 高的元素。请按升序排列。

你所设计算法的时间复杂度必须优于 O(n log n),其中 n 是数组大小。

输入

  • nums: 一个正整数数组
  • k: 一个整数

返回

返回一个包含 k 个元素的字符串,数字元素之间用逗号分隔。数字元素按升序排列,表示出现频率最高的 k 个元素。

参数限制

  • 1 <= nums[i] <= 10^4
  • 1 <= nums.length <= 10^5
  • k 的取值范围是 [1, 数组中不相同的元素的个数]
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

测试样例

样例1:

输入:nums = [1, 1, 1, 2, 2, 3], k = 2
输出:"1,2"
解释:元素 1 出现了 3 次,元素 2 出现了 2 次,元素 3 出现了 1 次。因此前两个高频元素是 1 和 2。

样例2:

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

样例3:

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

问题分析

根据题目要求,结果需要返回频率前k高的元素,并且按升序排序,这里很明显需要反复使用排序算法。同时问题要求时间复杂度不能超过O(nlogn)。因此对于排序算法的选择需要有要求。

在上一篇中,我提到了MarsCode在对算法学习方面的高效性,并总结了常用的排序算法。其中快速排序和归并排序的时间复杂度为O(nlogn)。因此我们选择其中一个(这里选择快速排序)解决问题。

问题解决分为三步:

  1. 需要统计每个元素出现的次数,我们定义一个最大的数组count,数组的下标表示每个元素,数组的值表示元素的个数,通过一次遍历可以得到每个元素的出现次数,这里属于空间换时间的做法。
  2. 得到count以后,利用快速排序做降序排序,排序后的前k个元素就是频率前k高元素出现的个数,但是我们要直到元素在原始count中的下标,所以还需要定义一个index下标数组,记录count元素对应的下标。
  3. index的前k个数才是频率前k高的元素,在对这k个数通过快速排序进行升序排序。最终转换成字符串后得到的结果为问题的解。

整个时间复杂度T= mlogm + klogk + n,m是元素最大上限,n是数组长度。因此时间复杂度T(n) = O(nlogn)。

完整代码如下

import java.util.Arrays;

public class Main {
    private static int flag = 0;

    public static void quickSort(int[] count,  int[] index, int left, int right){
        if(left < right){
            int pivot = partition(count, index, left, right);
            quickSort(count, index, left, pivot-1);
            quickSort(count, index,pivot+1, right);
        }
    }
    public static int partition(int[]count, int[] index, int left, int right){
        int pivot_index = index[left];
        int pivot_count = count[left];
        while(left < right){
            while(left < right && (flag==0? count[right] <= pivot_count : count[right] >= pivot_count)){
                right--;
            }
            count[left] = count[right]; // 按元素出现次数排序时吗,下标要随着修改。保证下标和元素的对应。
            index[left] = index[right];
            while(left < right && (flag==0? count[left] >= pivot_count : count[left] <= pivot_count)){
                left++;
            }
            count[right] = count[left];
            index[right] = index[left];
        }
        count[left] = pivot_count;
        index[left] = pivot_index;
        return left;
    }

//    public static int compare(int a, int count_a, int b, int b_count){
//        return count_a == b_count? b-a : count_a - b_count;
//    }

    public static String solution(int[] nums, int k) {
        // Please write your code here
        flag = 0; // flag = 0表示降序排序, flag=1表示升序排序。
        int count[] = new int[10001]; // 元素最大上限
        int index[] = new int[10001];
        Arrays.fill(count, 0);
        for(int i=0; i<nums.length; i++){
            count[nums[i]]++; // 每个元素出现的次数
        }
        for(int i=0; i<index.length; i++){
            index[i] = i; // 元素对饮的下标
        }
        // 快速排序
        quickSort(count, index, 0, count.length-1);
        int[] result = new int[k];
        for(int i=0; i<k; i++){
            result[i] = index[i];
        }
        flag = 1;
        quickSort(result,result, 0, k-1);

        StringBuilder res= new StringBuilder();
        for(int i=0; i<k-1; i++){
            res.append(result[i]).append(",");
        }
        res.append(result[k-1]);

//        System.out.println(res);
        return res.toString();
    }

    public static void main(String[] args) {
        //  You can add more test cases here
        int[] nums1 = {1, 1, 1, 2, 2, 3};
        int[] nums2 = {1};

        System.out.println(solution(nums1, 2).equals("1,2"));
        System.out.println(solution(nums2, 1).equals("1"));
    }
}

问题总结

本题主要考察排序算法的时间复杂度问题,需要对排序算法有较清晰的了解,当前通过MarsCode可以很便捷的给出提示,帮助我们学习。