[347].前 K 个高频元素

 


题目

给定一个非空的整数数组,返回其中出现频率前 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];
}

因为,size2 的幂,对 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;
}

排序的复杂度:

  • 时间复杂度: Θ ( n ∗ l o g n ) \Theta(n*logn) Θ(nlogn)
  • 空间复杂度: Θ ( n ) \Theta(n) Θ(n)
     

算法设计:优先队列 or 堆

上面排序的思路,还可以,但做了很多无用功。

题目只有前 k 个元素,我们却把所有元素都排了一次。

那有木有什么算法或者数据结构,它并非要等到所有的排序工作都做完的时候,才知道谁是前几名,而是可以只排出前几名。

有的,在二叉树这种数据结构中,有一种更特殊的细类,被称为“堆”,用这种数据结构,就可以做到只排出前几名,而不用管后面的名次。

如果只需要排出前 k 名,这种算法得到第一名的复杂度是 Θ ( n ) \Theta(n) Θ(n),而得到第二、第三、第四名等等的复杂度都只有 Θ ( l o g n ) \Theta(logn) Θ(logn)

思路:维护一个含有 k 个元素的优先队列(底层是堆实现),如果遍历到的元素比队列中的最小频率元素要高,我们就取出最小频率的元素,将新元素入队。最后,队列里剩下的就是前 k 个出现频率最高的元素啦。

正在更新ing...

优先队列的复杂度:

  • 时间复杂度: Θ ( n ∗ l o g n ) \Theta(n*logn) Θ(nlogn)
  • 空间复杂度: Θ ( n ) \Theta(n) Θ(n)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值