问题描述
给你一个整数数组 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)。因此我们选择其中一个(这里选择快速排序)解决问题。
问题解决分为三步:
- 需要统计每个元素出现的次数,我们定义一个最大的数组count,数组的下标表示每个元素,数组的值表示元素的个数,通过一次遍历可以得到每个元素的出现次数,这里属于空间换时间的做法。
- 得到count以后,利用快速排序做降序排序,排序后的前k个元素就是频率前k高元素出现的个数,但是我们要直到元素在原始count中的下标,所以还需要定义一个index下标数组,记录count元素对应的下标。
- 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可以很便捷的给出提示,帮助我们学习。