LeetCode:347. 前 K 个高频元素

347. 前 K 个高频元素

    

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

示例 1:

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

示例 2:

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

提示:

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

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

思路:

  1. 要统计元素出现次数(通过map:cnt)
  2. 对次数排序(通过优先级队列:pq)
  3. 找出前K个高频元素

时间复杂度:O(nlogk),n 表示数组的长度。首先,遍历一遍数组统计元素的频率,这一系列操作的时间复杂度是 O(n);接着,遍历用于存储元素频率的 map,如果元素的频率大于大顶堆中顶部的元素,则将顶部的元素删除并将该元素加入堆中,这里维护堆的数目是 k,所以这一系列操作的时间复杂度是 O(nlogk) 的;因此,总的时间复杂度是 O(nlog⁡k)。

空间复杂度: O(n),最坏情况下(每个元素都不同),map 需要存储 n 个键值对,优先队列需要存储 k 个元素,因此,空间复杂度是 O(n)。

vector<int> topKFrequent(vector<int>& nums, int k) {
    priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq; // 优先级队列(大顶堆)
    unordered_map<int, int> cnt;            // 负责统计每个元素出现的次数 key:元素值 value:元素出现次数

    // for (auto num : nums) cnt[num]++;
    for (int i = 0; i < nums.size(); i++)
    {
        cnt[nums[i]] += 1;
    }

    // for (auto kv : cnt) {
    //     pq.push({kv.second, kv.first});
    //     while (pq.size() > k) pq.pop();
    // }
    for (unordered_map<int, int>::iterator it = cnt.begin(); it != cnt.end(); it++)
    {
        pq.push({it->second, it->first});   // 根据map中的key(次数)来排序,value为次数对应的元素值
        if (pq.size() > k)                  // pq始终维护长度为k的队列(此处提前限定了pq长度为k,下面while不用for (int i = 0; i < k; i++) 了)
        {
            pq.pop();
        }
    }

    // vector<int> res;
    // while (!pq.empty())
    // {
    //     res.push_back(pq.top().second);
    //     pq.pop();
    // }
    // return res;
    vector<int> res;
    while (!pq.empty())
    {
        res.push_back(pq.top().second);     // 从队首依次从大到小获取每个元素的second域(元素值),并存入v
        pq.pop();
    }
    return res;
}

这道题做的快自闭了,STL的优先级队列刚开始自己写语法都有问题 = = !哎难受~ 


补充一波Go实现的解法,不过可能时间复杂度不满足题目要求的优于 O(n log n) ,因为排序过程也有自己的时间复杂度,且算是一种方法吧 - - !

func topKFrequent(nums []int, k int) []int {
    // 1.利用map统计每个元素出现频次
    m := make(map[int]int)
	for _, val := range nums {
		m[val] += 1
	}

    // 2.将map转为结构体数组,再利用sort包按结构体的Val"从大到小"排序
	type KV struct {
		Key int
		Val int
	}

	kvSlice := make([]KV, 0)
	for k, v := range m {
		kv := KV{k, v}
		kvSlice = append(kvSlice, kv)
	}

	sort.Slice(kvSlice, func(i, j int) bool {   // 对结构体的val值"从大到小"排序
		return kvSlice[i].Val > kvSlice[j].Val
	})

	fmt.Println(kvSlice)

    // 3.输出前k个高频元素
	res := make([]int, 0)
	for i := 0; i < k; i++ {
		res = append(res, kvSlice[i].Key)
	}

	return res
}


// 上述过程简化版:
func topKFrequent(nums []int, k int) []int {
    m := make(map[int]int, 0)
    
    for i := 0; i < len(nums); i++ {
        m[nums[i]] += 1
    }

    // 去重后的nums数组
    res := make([]int, 0)
    for k, _ := range m {
        res = append(res, k)
    }

    // 根据【出现频次】对【去重后的nums数组】排序
    sort.Slice(res, func(i, j int) bool {
        return m[res[i]] > m[res[j]]
    })

    return res[:k]
}

// 另一种方法 堆排序:
// 建立大小为k的 小顶堆,将map统计的频次依次加入堆中。后续如果出现频次更高的元素,那就替换掉小顶堆堆顶元素即可~
// 时间复杂度:O(nlogk)

// 那为什么不能用大顶堆呢?
// 你想啊,如果定义一个大小为k的大顶堆,在每次移动更新大顶堆的时候,每次弹出都把最大的元素弹出去了,那么怎么保留下来前K个高频元素呢。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值