今天刷的也是一道难度特别难的题目:二进制之和
问题描述
给你一个整数数组 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"
题目解析与解题思路
我们需要从一个数组 nums 中找出出现频率最高的 k 个元素,并返回它们按升序排列的结果,以逗号分隔的字符串形式。
为了达到这个目标,我们可以采取以下步骤:
1. 统计元素频率
首先,我们需要统计数组中每个元素出现的频率。可以利用 Python 中的 collections.Counter 类,这个类提供了一个简单的方法来统计元素的出现次数,它的返回值是一个字典(dict),键是元素,值是对应元素的出现次数。
2. 按频率排序
接下来,我们需要根据元素的出现频率对这些元素进行排序:
- 按出现频率从高到低排序。
- 如果出现频率相同,则按元素的值升序排序。
我们可以使用 Python 的 sorted 函数来完成排序。sorted 函数允许我们自定义排序规则,通过 key 参数可以指定排序的依据。在此,我们需要根据两个条件排序:频率和元素值。
3. 提取前 k 个高频元素
从排序后的结果中提取出前 k 个频率最高的元素。此时,我们可以直接获取前 k 个元素,存储它们的值。
4. 返回格式化结果
最后,将提取出来的前 k 个元素按升序排列(因为题目要求返回的是升序排列),并将其转换为以逗号分隔的字符串。
5. 时间复杂度分析
-
统计频率: 使用
Counter来统计频率的时间复杂度是O(n),其中n是数组的长度。Counter会遍历一次数组,生成频率字典。 -
排序: 我们对所有不同的元素进行排序。假设数组中有
m个不同的元素,排序操作的时间复杂度是O(m log m)。在最坏情况下(数组中没有重复元素),m等于n,因此排序的复杂度是O(n log n)。 -
提取前
k个元素: 提取前k个高频元素的时间复杂度是O(k),其中k是题目给定的数量。由于k通常小于n,这一部分的复杂度可以忽略不计。
综上,总时间复杂度是 O(n + n log n),其中 n 是数组的长度。
算法实现
from collections import Counter
def topKFrequent(nums, k):
# Step 1: 统计每个元素出现的频率
count = Counter(nums)
# Step 2: 根据频率排序(按频率降序,如果频率相同则按元素值升序)
sorted_elements = sorted(count.items(), key=lambda x: (-x[1], x[0]))
# Step 3: 提取前 k 个高频元素
top_k = [x[0] for x in sorted_elements[:k]]
# Step 4: 返回按升序排列的字符串
return ",".join(map(str, sorted(top_k)))
# 示例测试
print(topKFrequent([1, 1, 1, 2, 2, 3], 2)) # 输出: "1,2"
print(topKFrequent([1], 1)) # 输出: "1"
print(topKFrequent([4, 4, 4, 2, 2, 2, 3, 3, 1], 2)) # 输出: "2,4"
代码讲解
-
Counter(nums): 通过Counter对nums数组中的元素进行计数。返回的count是一个字典,键是元素,值是该元素在nums中出现的次数。 -
sorted(count.items(), key=lambda x: (-x[1], x[0])):count.items()返回一个包含所有元素及其频率的元组列表。每个元组的形式是(元素, 频率)。key=lambda x: (-x[1], x[0])指定排序规则:-x[1]:按频率降序排序(频率大的排前面)。x[0]:如果频率相同,则按元素值升序排序(值小的排前面)。
-
top_k = [x[0] for x in sorted_elements[:k]]:- 从排序后的列表中提取前
k个元素,x[0]是元组中的元素值。
- 从排序后的列表中提取前
-
",".join(map(str, sorted(top_k))):sorted(top_k)对提取出的前k个元素进行升序排列。",".join(map(str, ...))将升序排列后的元素列表转换为以逗号分隔的字符串。
总结
该算法通过使用 Counter 进行频率统计,并结合 sorted 函数实现了对元素的排序和提取。最终,我们能够高效地返回出现频率最高的 k 个元素,并满足题目要求的格式。这道题目可以归类为 频率排序与选择题,其解题思路是基于 哈希表(统计频率)与 排序(根据频率和数字值排序)。解题的联想思维是从频率分析和排序规律出发:
- 统计频率:就像解联想题时找出不同元素间的关系一样,首先要理解每个元素的“重要性”,即它出现的次数。
- 按频率排序:这一步类似于对不同事物按重要性排序,先高后低,有时还需要细化排序规则(如按数字值)。
- 提取前 k 个元素:就像联想题中挑选出最相关的几个元素一样,我们从排序后的结果中选择最重要的几个。
- 最终输出:最后整理这些元素,像联想题的答案一样,按特定顺序排列并输出。
哈希表(Hash Table)是一种数据结构,它能够实现 常数时间(即平均时间复杂度为 O(1))的元素查找、插入和删除操作。它的核心思想是通过一个哈希函数将数据映射到一个固定大小的数组中,从而实现高效的数据访问。
哈希表的基本概念
-
哈希函数(Hash Function):
- 哈希函数是将输入(通常是一个键)映射为一个整数值(哈希值)。这个整数值决定了键值对存储在哈希表中的位置。
- 理想情况下,哈希函数应该将键均匀地分布到整个哈希表中,减少哈希冲突。
-
哈希冲突(Collision):
- 哈希冲突发生在不同的键经过哈希函数映射后得到了相同的哈希值。冲突的解决通常有两种常见的方法:
- 开放定址法(Open Addressing):当发生冲突时,尝试找到另一个空的位置来存放冲突的元素。
- 链式法(Chaining):每个哈希表的槽位不仅存储一个元素,而是一个链表,所有哈希值相同的元素都存储在同一个链表中。
- 哈希冲突发生在不同的键经过哈希函数映射后得到了相同的哈希值。冲突的解决通常有两种常见的方法:
-
哈希表的结构: 哈希表一般包含两个主要部分:
- 键(Key):用于存储和查找数据的唯一标识。
- 值(Value):与键相关联的数据。
哈希表通常是一个数组(或列表),而数组的索引位置是由键经过哈希函数计算得出的。
哈希表的基本操作
-
插入(Insert):
- 给定一个键值对,将该元素插入到哈希表中。通过哈希函数计算出键的哈希值,并将该值存放在相应位置。如果发生冲突,使用合适的冲突解决方法。
-
查找(Search):
- 查找哈希表中是否存在某个键。如果存在,返回对应的值;否则返回未找到的结果。
-
删除(Delete):
- 删除哈希表中的某个键值对,通过哈希函数找到该键的索引并删除对应的元素。
哈希表的优点
- 快速查找、插入和删除:哈希表能够实现常数时间的查找和插入操作(平均情况下)。
- 灵活性:哈希表可以存储任意类型的键值对,适用于许多需要高效查找的数据处理场景。
哈希表的缺点
- 内存占用较高:由于哈希表需要预留足够的空间来减少冲突,它的空间利用率可能不高。
- 哈希冲突问题:冲突的处理可能会影响查找效率,尤其是在冲突多的情况下。
- 哈希函数设计复杂:一个好的哈希函数需要均匀分布,设计不好可能会导致频繁的冲突。
哈希表的应用场景
- 数据库索引:哈希表常用于数据库的索引系统,快速查找记录。
- 缓存实现:哈希表能够高效地实现缓存,减少重复计算。
- 计数问题:例如,统计元素出现的频率。使用哈希表存储元素及其对应的频率,查找和更新操作都很高效。
- 集合操作:哈希表常用于实现集合(如
set类型),支持快速插入、删除和查找操作。
哈希表的总结
哈希表是一种非常高效的数据结构,广泛应用于许多实际场景中。它能够以常数时间完成元素的查找、插入和删除,是处理大规模数据时非常重要的工具。虽然在实际应用中需要注意哈希冲突和内存管理等问题,但其基本操作的效率使得它成为许多算法和系统中的核心组件。