前 K 个高频元素
- 题目
- 函数原型
- 边界判断
- 算法设计:排序
- 算法设计:优先队列 or 堆
题目
给定一个非空的整数数组,返回其中出现频率前 k
高的元素。
示例 1:
输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]
示例 2:
输入: nums = [1], k = 1
输出: [1]
函数原型
C
的函数原型:
int* topKFrequent(int* nums, int numsSize, int k, int* returnSize){}
边界判断
int* topKFrequent(int* nums, int numsSize, int k, int* returnSize){
if( nums == NULL || numsSize == NULL || k == 0 )
return 0;
}
算法设计:排序
思路:得先扫描一次,统计好频率,排序找到前 k 个元素出现频率最高的元素。
- 扫描统计频率
- 排序
typedef struct {
int cnt;
int val;
}Bucket; // 桶排序的数据类型定义,加一个 val 就很方便了。
int cmps(const void *a, const void *b){
return ((Bucket*)a)->cnt < ((Bucket*)b)->cnt; // 结构体排序,以频次为标准
}
#define size 1024
int* topKFrequent(int* nums, int numsSize, int k, int* returnSize)
{
if( nums == NULL || numsSize == NULL || k == 0 )
return 0;
Bucket *bucket = (Bucket*)malloc(sizeof(Bucket) * size);
memset(bucket, 0, sizeof(Bucket) * size);
// 1. 计数
for(int i=0; i<numsSize; i++){
bucket[nums[i]].cnt ++;
bucket[nums[i]].val = nums[i];
}
// 2. 排序
qsort(bucket, size, sizeof(bucket[0]), cmps);
for (int i = 0; i < k; i++)
nums[i] = bucket[i].val;
*returnSize = k;
free(bucket);
return nums;
}
但元素是负数,这时,按下标赋值就会越界访问。
/* 1. 计数 */
#define OFFSET 1<<10 // 对下标为负数进行偏移使数组下标 >= 0
for(int i=0; i<numsSize; i++){
bucket[(nums[i]+OFFSET)%size].cnt ++;
bucket[(nums[i]+OFFSET)%size].val = nums[i];
}
因为,size
是 2
的幂,对 2
的幂取模,可以改成位运算。
/* 1. 计数 */
#define OFFSET (1<<10) // 对下标为负数进行偏移使数组下标 >= 0
for(int i=0; i<numsSize; i++){
bucket[(nums[i]+OFFSET)&(size-1)].cnt ++;
bucket[(nums[i]+OFFSET)&(size-1)].val = nums[i];
}
完整代码:
typedef struct {
int cnt;
int val; // 哈希查找,加一个 val 就很方便了
}Bucket;
int cmps(const void *a, const void *b){
return ((Bucket*)a)->cnt < ((Bucket*)b)->cnt; // 结构体排序,以频次为标准
}
int* topKFrequent(int* nums, int numsSize, int k, int* returnSize)
{
if( nums == NULL || numsSize == NULL || k == 0 )
return 0;
#define size (1<<11)
Bucket *bucket = (Bucket*)malloc(sizeof(Bucket) * size);
memset(bucket, 0, sizeof(Bucket) * size);
// 1. 计数
#define OFFSET (1<<10) // 对下标为负数进行偏移使数组下标 >= 0
for(int i=0; i<numsSize; i++){
bucket[(nums[i]+OFFSET)&(size-1)].cnt ++;
bucket[(nums[i]+OFFSET)&(size-1)].val = nums[i];
}
// 2. 排序
qsort(bucket, size, sizeof(bucket[0]), cmps);
for (int i = 0; i < k; i++)
nums[i] = bucket[i].val;
*returnSize = k;
free(bucket);
return nums;
}
排序的复杂度:
- 时间复杂度:
- 空间复杂度:
算法设计:优先队列 or 堆
上面排序的思路,还可以,但做了很多无用功。
题目只有前 k
个元素,我们却把所有元素都排了一次。
那有木有什么算法或者数据结构,它并非要等到所有的排序工作都做完的时候,才知道谁是前几名,而是可以只排出前几名。
有的,在二叉树这种数据结构中,有一种更特殊的细类,被称为“堆”,用这种数据结构,就可以做到只排出前几名,而不用管后面的名次。
如果只需要排出前 k
名,这种算法得到第一名的复杂度是,而得到第二、第三、第四名等等的复杂度都只有
。
思路:维护一个含有 k 个元素的优先队列(底层是堆实现),如果遍历到的元素比队列中的最小频率元素要高,我们就取出最小频率的元素,将新元素入队。最后,队列里剩下的就是前 k 个出现频率最高的元素啦。
正在更新ing...
优先队列的复杂度:
- 时间复杂度:
- 空间复杂度: