【LeetCode 热题 100】347. 前 K 个高频元素——(解法一)排序截取
Problem: 347. 前 K 个高频元素
给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。
文章目录
- 整体思路
- 完整代码
- 时空复杂度
- 时间复杂度:O(N + M log M)
- 空间复杂度:O(M)
整体思路
这段代码同样旨在解决 “前 K 个高频元素” 问题。此版本的实现策略非常直接,可以概括为 “先统计,后排序,再截取”,充分利用了Java的集合框架和Collections
工具类。
算法的整体思路可以清晰地分解为以下三个步骤:
-
第一步:频率统计
- 算法首先使用一个 哈希表 (HashMap)
frequencyMap
来高效地统计数组nums
中每个数字出现的频率。 - 遍历结束后,
frequencyMap
中存储了{数字 -> 出现次数}
的完整映射关系。
- 算法首先使用一个 哈希表 (HashMap)
-
第二步:转换为列表并排序
- 由于
HashMap
本身是无序的,无法直接按值排序。因此,算法需要将map
中的数据转换到一个可以排序的结构中。 - 转换:通过
new ArrayList<>(frequencyMap.entrySet())
,将map
的键值对集合(entrySet
)转换成一个ArrayList
。列表中的每个元素都是一个Map.Entry<Integer, Integer>
对象。 - 排序:调用
Collections.sort()
对这个entryList
进行排序。这是算法的核心操作。- 它使用了一个自定义的比较器(通过lambda表达式
(a, b) -> (b.getValue() - a.getValue())
提供),该比较器指示sort
方法应该根据Map.Entry
的值(getValue()
,即频率)来进行比较。 b.getValue() - a.getValue()
的结果决定了排序顺序。当b
的频率大于a
时,结果为正,b
会被排在a
的前面,从而实现了按频率降序排序。
- 它使用了一个自定义的比较器(通过lambda表达式
- 由于
-
第三步:提取结果
- 经过排序后,
entryList
的前k
个元素就是频率最高的k
个键值对。 - 算法创建一个大小为
k
的结果数组ans
,然后通过一个简单的for
循环,遍历排序后列表的前k
项,提取出每个Entry
的键(getKey()
,即原始数字),并存入ans
数组。 - 最后返回
ans
数组。
- 经过排序后,
这个方法逻辑清晰,代码可读性好,但由于对所有唯一元素进行了完全排序,其性能在 k
远小于唯一元素数量 M
时,不如使用最小堆的 O(N log k) 方法。
完整代码
class Solution {/*** 找出数组中出现频率最高的前 k 个元素。* @param nums 整数数组* @param k 需要找出的元素个数* @return 包含前 k 个高频元素的数组*/public int[] topKFrequent(int[] nums, int k) {// 步骤 1: 使用 HashMap 统计每个数字的出现频率Map<Integer, Integer> frequencyMap = new HashMap<>();for (int num : nums) {frequencyMap.put(num, frequencyMap.getOrDefault(num, 0) + 1);}// 步骤 2: 将 Map 的条目转换为列表,以便进行排序List<Map.Entry<Integer, Integer>> entryList = new ArrayList<>(frequencyMap.entrySet());// 使用 Collections.sort 和自定义比较器,按频率(entry 的 value)进行降序排序// (a, b) -> (b.getValue() - a.getValue()) 实现了降序排列Collections.sort(entryList, (a, b) -> (b.getValue() - a.getValue()));// 步骤 3: 提取排序后列表的前 k 个元素的键(即原始数字)int[] ans = new int[k];for (int i = 0; i < k; i++) {ans[i] = entryList.get(i).getKey();} return ans;}
}
时空复杂度
时间复杂度:O(N + M log M)
- 频率统计:遍历
nums
数组一次,填充哈希表。时间复杂度为 O(N),其中N
是nums
的长度。 - Map 转 List:将哈希表中的
M
个唯一元素的条目复制到列表中。时间复杂度为 O(M),其中M
是唯一元素的数量。 - 排序 (
Collections.sort
):这是时间开销最大的部分。对包含M
个条目的列表进行排序,其时间复杂度为 O(M log M)。 - 提取结果:遍历排序后列表的前
k
个元素,时间复杂度为 O(k)。
综合分析:
总时间复杂度 = O(N) + O(M) + O(M log M) + O(k)。
由于 k <= M <= N
,其中 O(M log M)
是主导项(除非 N
异常巨大而 M
极小)。
因此,最终的时间复杂度可以写为 O(N + M log M)。
空间复杂度:O(M)
- 主要存储开销:
HashMap frequencyMap
: 需要存储所有M
个唯一元素及其频率。空间为 O(M)。List<Map.Entry> entryList
: 需要存储这M
个键值对。空间为 O(M)。- 排序的内部空间:
Collections.sort
(在后台使用Timsort
)对列表排序时,会需要一些额外的空间,最坏情况下为 O(M)。
综合分析:
算法所需的额外空间主要由哈希表和列表决定。因此,总的空间复杂度为 O(M),其中 M
是数组中唯一元素的数量。